summaryrefslogtreecommitdiff
path: root/toolkit/devtools/debugger
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2016-10-16 19:34:53 -0400
committerMatt A. Tobin <email@mattatobin.com>2016-10-16 19:34:53 -0400
commit81805ce3f63e2e4a799bd54f174083c58a9b5640 (patch)
tree6e13374b213ac9b2ae74c25d8aac875faf71fdd0 /toolkit/devtools/debugger
parent28c8da71bf521bb3ee76f27b8a241919e24b7cd5 (diff)
downloadpalemoon-gre-81805ce3f63e2e4a799bd54f174083c58a9b5640.tar.gz
Move Mozilla DevTools to Platform - Part 3: Merge the browser/devtools and toolkit/devtools adjusting for directory collisions
Diffstat (limited to 'toolkit/devtools/debugger')
-rw-r--r--toolkit/devtools/debugger/debugger-commands.js604
-rw-r--r--toolkit/devtools/debugger/debugger-controller.js2427
-rw-r--r--toolkit/devtools/debugger/debugger-panes.js3420
-rw-r--r--toolkit/devtools/debugger/debugger-toolbar.js1594
-rw-r--r--toolkit/devtools/debugger/debugger-view.js847
-rw-r--r--toolkit/devtools/debugger/debugger.css50
-rw-r--r--toolkit/devtools/debugger/debugger.xul524
-rw-r--r--toolkit/devtools/debugger/moz.build11
-rw-r--r--toolkit/devtools/debugger/panel.js126
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon3/lib/main.js13
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon3/package.json9
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/bootstrap.js34
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/chrome.manifest1
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/install.rdf19
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/test.jsm6
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/test.xul8
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/test2.jsm6
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/test2.xul8
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/testxul.js4
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/testxul2.js4
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/bootstrap.js23
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/chrome.manifest1
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/install.rdf20
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/test.jsm6
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/test.xul8
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/test2.jsm6
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/test2.xul8
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/testxul.js4
-rw-r--r--toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/testxul2.js4
-rw-r--r--toolkit/devtools/debugger/test/addon1.xpibin0 -> 71453 bytes
-rw-r--r--toolkit/devtools/debugger/test/addon2.xpibin0 -> 71455 bytes
-rw-r--r--toolkit/devtools/debugger/test/addon3.xpibin0 -> 7423 bytes
-rw-r--r--toolkit/devtools/debugger/test/addon4.xpibin0 -> 3049 bytes
-rw-r--r--toolkit/devtools/debugger/test/addon5.xpibin0 -> 2933 bytes
-rw-r--r--toolkit/devtools/debugger/test/browser.ini562
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_aaa_run_first_leaktest.js27
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_addon-console.js44
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_addon-modules-unpacked.js61
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_addon-modules.js61
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_addon-panels.js51
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_addon-sources.js36
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_addonactor.js98
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_auto-pretty-print-01.js110
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_auto-pretty-print-02.js119
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_bfcache.js89
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_blackboxing-01.js52
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_blackboxing-02.js55
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_blackboxing-03.js59
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_blackboxing-04.js60
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_blackboxing-05.js69
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_blackboxing-06.js55
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breadcrumbs-access.js86
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_break-on-dom-01.js52
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_break-on-dom-02.js126
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_break-on-dom-03.js89
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_break-on-dom-04.js97
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_break-on-dom-05.js124
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_break-on-dom-06.js123
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_break-on-dom-07.js104
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_break-on-dom-08.js54
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_break-on-dom-event-01.js229
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_break-on-dom-event-02.js109
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-actual-location.js96
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-actual-location2.js102
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js108
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-button-01.js60
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-button-02.js69
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-contextmenu-add.js95
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-contextmenu.js322
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-disabled-reload.js118
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-editor.js301
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-eval.js46
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-highlight.js103
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-new-script.js88
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-other-tabs.js35
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-pane.js254
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_breakpoints-reload.js35
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_chrome-create.js62
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_chrome-debugging.js100
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_clean-exit-window.js84
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_clean-exit.js38
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_closure-inspection.js148
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_cmd-blackbox.js114
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_cmd-break.js221
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_cmd-dbg.js100
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-01.js243
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-02.js202
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-03.js90
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-04.js92
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_controller-evaluate-01.js91
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_controller-evaluate-02.js66
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_debugger-statement.js89
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_editor-contextmenu.js62
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_editor-mode.js91
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_event-listeners-01.js151
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_event-listeners-02.js127
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_event-listeners-03.js86
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_file-reload.js63
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_function-display-name.js61
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_global-method-override.js20
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_globalactor.js62
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_hide-toolbar-buttons.js32
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_hit-counts-01.js61
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_hit-counts-02.js66
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_host-layout.js104
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_iframes.js67
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_instruments-pane-collapse.js152
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_interrupts.js85
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_listaddons.js114
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_listtabs-01.js101
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_listtabs-02.js218
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_listtabs-03.js80
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_location-changes-01-simple.js69
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_location-changes-02-blank.js60
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_location-changes-03-new.js62
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_location-changes-04-breakpoint.js201
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_multiple-windows.js169
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_navigation.js73
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_no-page-sources.js50
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_on-pause-highlight.js75
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_on-pause-raise.js139
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_optimized-out-vars.js45
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_panel-size.js82
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_parser-01.js31
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_parser-02.js26
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_parser-03.js77
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_parser-04.js54
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_parser-05.js43
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_parser-06.js78
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_parser-07.js55
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_parser-08.js289
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_parser-09.js290
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_parser-10.js127
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pause-exceptions-01.js240
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pause-exceptions-02.js196
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pause-resume.js76
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pause-warning.js98
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_paused-keybindings.js45
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pretty-print-01.js82
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pretty-print-02.js55
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pretty-print-03.js49
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pretty-print-04.js70
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pretty-print-05.js78
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pretty-print-06.js92
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pretty-print-07.js57
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pretty-print-08.js94
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pretty-print-09.js87
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pretty-print-10.js61
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pretty-print-11.js58
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pretty-print-12.js52
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pretty-print-13.js85
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_pretty-print-on-paused.js64
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_progress-listener-bug.js85
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_reload-preferred-script-01.js50
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_reload-preferred-script-02.js44
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_reload-preferred-script-03.js57
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_reload-same-script.js77
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_scripts-switching-01.js192
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_scripts-switching-02.js182
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_scripts-switching-03.js50
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-autofill-identifier.js135
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-basic-01.js317
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-basic-02.js122
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-basic-03.js118
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-basic-04.js130
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-global-01.js272
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-global-02.js217
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-global-03.js104
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-global-04.js92
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-global-05.js154
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-global-06.js119
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-popup-jank.js117
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-sources-01.js233
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-sources-02.js276
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-sources-03.js98
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_search-symbols.js467
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_searchbox-help-popup-01.js58
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_searchbox-help-popup-02.js84
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_searchbox-parse.js124
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-01.js250
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-02.js191
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-03.js82
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-04.js84
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_source-maps-01.js167
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_source-maps-02.js148
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_source-maps-03.js83
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_source-maps-04.js183
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_sources-bookmarklet.js50
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_sources-cache.js142
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_sources-eval-01.js39
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_sources-eval-02.js48
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_sources-labels.js166
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_sources-sorting.js136
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_split-console-paused-reload.js42
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_stack-01.js43
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_stack-02.js109
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_stack-03.js73
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_stack-04.js52
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_stack-05.js129
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_stack-06.js86
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_stack-07.js107
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_step-out.js79
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_tabactor-01.js70
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_tabactor-02.js84
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_terminate-on-tab-close.js36
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_tracing-01.js105
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_tracing-02.js74
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_tracing-03.js70
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_tracing-04.js80
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_tracing-05.js81
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_tracing-06.js37
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_tracing-07.js83
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_tracing-08.js59
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-01.js126
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-02.js221
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-03.js151
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-04.js150
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-05.js228
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-06.js122
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-accessibility.js555
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-data.js609
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-cancel.js52
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-click.js52
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-getset-01.js294
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-getset-02.js101
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-value.js85
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-watch.js502
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-01.js218
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-02.js225
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-03.js157
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-04.js225
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-05.js233
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-pref.js79
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-searchbox.js144
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-parameters-01.js256
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-parameters-02.js546
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-parameters-03.js151
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-with.js207
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-frozen-sealed-nonext.js87
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-hide-non-enums.js105
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js239
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-override-01.js227
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-override-02.js67
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-01.js59
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-02.js45
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-03.js42
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-04.js31
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-05.js50
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-06.js76
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-07.js63
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-08.js67
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-09.js32
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-10.js60
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-11.js77
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-12.js70
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-13.js61
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-14.js48
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-15.js32
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-16.js67
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-reexpand-01.js201
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-reexpand-02.js216
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-reexpand-03.js123
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_variables-view-webidl.js256
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_watch-expressions-01.js227
-rw-r--r--toolkit/devtools/debugger/test/browser_dbg_watch-expressions-02.js371
-rw-r--r--toolkit/devtools/debugger/test/code_binary_search.coffee18
-rw-r--r--toolkit/devtools/debugger/test/code_binary_search.js29
-rw-r--r--toolkit/devtools/debugger/test/code_binary_search.map10
-rw-r--r--toolkit/devtools/debugger/test/code_blackboxing_blackboxme.js9
-rw-r--r--toolkit/devtools/debugger/test/code_blackboxing_one.js4
-rw-r--r--toolkit/devtools/debugger/test/code_blackboxing_three.js4
-rw-r--r--toolkit/devtools/debugger/test/code_blackboxing_two.js4
-rw-r--r--toolkit/devtools/debugger/test/code_breakpoints-break-on-last-line-of-script-on-reload.js6
-rw-r--r--toolkit/devtools/debugger/test/code_breakpoints-other-tabs.js4
-rw-r--r--toolkit/devtools/debugger/test/code_frame-script.js33
-rw-r--r--toolkit/devtools/debugger/test/code_function-search-01.js42
-rw-r--r--toolkit/devtools/debugger/test/code_function-search-02.js21
-rw-r--r--toolkit/devtools/debugger/test/code_function-search-03.js32
-rw-r--r--toolkit/devtools/debugger/test/code_location-changes.js7
-rw-r--r--toolkit/devtools/debugger/test/code_math.js45
-rw-r--r--toolkit/devtools/debugger/test/code_math.map8
-rw-r--r--toolkit/devtools/debugger/test/code_math.min.js2
-rw-r--r--toolkit/devtools/debugger/test/code_math_bogus_map.js4
-rw-r--r--toolkit/devtools/debugger/test/code_same-line-functions.js1
-rw-r--r--toolkit/devtools/debugger/test/code_script-eval.js10
-rw-r--r--toolkit/devtools/debugger/test/code_script-switching-01.js6
-rw-r--r--toolkit/devtools/debugger/test/code_script-switching-02.js13
-rw-r--r--toolkit/devtools/debugger/test/code_test-editor-mode6
-rw-r--r--toolkit/devtools/debugger/test/code_tracing-01.js29
-rw-r--r--toolkit/devtools/debugger/test/code_ugly-2.js1
-rw-r--r--toolkit/devtools/debugger/test/code_ugly-3.js1
-rw-r--r--toolkit/devtools/debugger/test/code_ugly-4.js24
-rw-r--r--toolkit/devtools/debugger/test/code_ugly-5.js14
-rw-r--r--toolkit/devtools/debugger/test/code_ugly-6.js5
-rw-r--r--toolkit/devtools/debugger/test/code_ugly-7.js5
-rw-r--r--toolkit/devtools/debugger/test/code_ugly-83
-rw-r--r--toolkit/devtools/debugger/test/code_ugly-8^headers^1
-rw-r--r--toolkit/devtools/debugger/test/code_ugly.js3
-rw-r--r--toolkit/devtools/debugger/test/doc_auto-pretty-print-01.html14
-rw-r--r--toolkit/devtools/debugger/test/doc_auto-pretty-print-02.html14
-rw-r--r--toolkit/devtools/debugger/test/doc_binary_search.html15
-rw-r--r--toolkit/devtools/debugger/test/doc_blackboxing.html26
-rw-r--r--toolkit/devtools/debugger/test/doc_breakpoint-move.html25
-rw-r--r--toolkit/devtools/debugger/test/doc_breakpoints-break-on-last-line-of-script-on-reload.html8
-rw-r--r--toolkit/devtools/debugger/test/doc_breakpoints-other-tabs.html8
-rw-r--r--toolkit/devtools/debugger/test/doc_breakpoints-reload.html13
-rw-r--r--toolkit/devtools/debugger/test/doc_closure-optimized-out.html34
-rw-r--r--toolkit/devtools/debugger/test/doc_closures.html32
-rw-r--r--toolkit/devtools/debugger/test/doc_cmd-break.html22
-rw-r--r--toolkit/devtools/debugger/test/doc_cmd-dbg.html40
-rw-r--r--toolkit/devtools/debugger/test/doc_conditional-breakpoints.html35
-rw-r--r--toolkit/devtools/debugger/test/doc_domnode-variables.html24
-rw-r--r--toolkit/devtools/debugger/test/doc_editor-mode.html20
-rw-r--r--toolkit/devtools/debugger/test/doc_empty-tab-01.html14
-rw-r--r--toolkit/devtools/debugger/test/doc_empty-tab-02.html14
-rw-r--r--toolkit/devtools/debugger/test/doc_event-listeners-01.html43
-rw-r--r--toolkit/devtools/debugger/test/doc_event-listeners-02.html53
-rw-r--r--toolkit/devtools/debugger/test/doc_event-listeners-03.html63
-rw-r--r--toolkit/devtools/debugger/test/doc_frame-parameters.html37
-rw-r--r--toolkit/devtools/debugger/test/doc_function-display-name.html31
-rw-r--r--toolkit/devtools/debugger/test/doc_function-search.html30
-rw-r--r--toolkit/devtools/debugger/test/doc_global-method-override.html16
-rw-r--r--toolkit/devtools/debugger/test/doc_iframes.html15
-rw-r--r--toolkit/devtools/debugger/test/doc_included-script.html22
-rw-r--r--toolkit/devtools/debugger/test/doc_inline-debugger-statement.html21
-rw-r--r--toolkit/devtools/debugger/test/doc_inline-script.html25
-rw-r--r--toolkit/devtools/debugger/test/doc_large-array-buffer.html27
-rw-r--r--toolkit/devtools/debugger/test/doc_minified.html14
-rw-r--r--toolkit/devtools/debugger/test/doc_minified_bogus_map.html14
-rw-r--r--toolkit/devtools/debugger/test/doc_native-event-handler.html22
-rw-r--r--toolkit/devtools/debugger/test/doc_no-page-sources.html11
-rw-r--r--toolkit/devtools/debugger/test/doc_pause-exceptions.html35
-rw-r--r--toolkit/devtools/debugger/test/doc_pretty-print-2.html15
-rw-r--r--toolkit/devtools/debugger/test/doc_pretty-print-3.html8
-rw-r--r--toolkit/devtools/debugger/test/doc_pretty-print-on-paused.html14
-rw-r--r--toolkit/devtools/debugger/test/doc_pretty-print.html8
-rw-r--r--toolkit/devtools/debugger/test/doc_promise.html30
-rw-r--r--toolkit/devtools/debugger/test/doc_random-javascript.html15
-rw-r--r--toolkit/devtools/debugger/test/doc_recursion-stack.html35
-rw-r--r--toolkit/devtools/debugger/test/doc_same-line-functions.html15
-rw-r--r--toolkit/devtools/debugger/test/doc_scope-variable-2.html30
-rw-r--r--toolkit/devtools/debugger/test/doc_scope-variable-3.html23
-rw-r--r--toolkit/devtools/debugger/test/doc_scope-variable-4.html25
-rw-r--r--toolkit/devtools/debugger/test/doc_scope-variable.html25
-rw-r--r--toolkit/devtools/debugger/test/doc_script-bookmarklet.html14
-rw-r--r--toolkit/devtools/debugger/test/doc_script-eval.html16
-rw-r--r--toolkit/devtools/debugger/test/doc_script-switching-01.html18
-rw-r--r--toolkit/devtools/debugger/test/doc_script-switching-02.html18
-rw-r--r--toolkit/devtools/debugger/test/doc_split-console-paused-reload.html20
-rw-r--r--toolkit/devtools/debugger/test/doc_step-out.html42
-rw-r--r--toolkit/devtools/debugger/test/doc_terminate-on-tab-close.html20
-rw-r--r--toolkit/devtools/debugger/test/doc_tracing-01.html20
-rw-r--r--toolkit/devtools/debugger/test/doc_watch-expression-button.html31
-rw-r--r--toolkit/devtools/debugger/test/doc_watch-expressions.html29
-rw-r--r--toolkit/devtools/debugger/test/doc_with-frame.html29
-rw-r--r--toolkit/devtools/debugger/test/head.js1019
-rw-r--r--toolkit/devtools/debugger/test/sjs_random-javascript.sjs11
-rw-r--r--toolkit/devtools/debugger/test/testactors.js31
358 files changed, 40437 insertions, 0 deletions
diff --git a/toolkit/devtools/debugger/debugger-commands.js b/toolkit/devtools/debugger/debugger-commands.js
new file mode 100644
index 000000000..72824c446
--- /dev/null
+++ b/toolkit/devtools/debugger/debugger-commands.js
@@ -0,0 +1,604 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const gcli = require("gcli/index");
+
+loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
+
+/**
+ * The commands and converters that are exported to GCLI
+ */
+exports.items = [];
+
+/**
+ * Utility to get access to the current breakpoint list.
+ *
+ * @param DebuggerPanel dbg
+ * The debugger panel.
+ * @return array
+ * An array of objects, one for each breakpoint, where each breakpoint
+ * object has the following properties:
+ * - url: the URL of the source file.
+ * - label: a unique string identifier designed to be user visible.
+ * - lineNumber: the line number of the breakpoint in the source file.
+ * - lineText: the text of the line at the breakpoint.
+ * - truncatedLineText: lineText truncated to MAX_LINE_TEXT_LENGTH.
+ */
+function getAllBreakpoints(dbg) {
+ let breakpoints = [];
+ let sources = dbg._view.Sources;
+ let { trimUrlLength: trim } = dbg.panelWin.SourceUtils;
+
+ for (let source of sources) {
+ for (let { attachment: breakpoint } of source) {
+ breakpoints.push({
+ url: source.attachment.source.url,
+ label: source.attachment.label + ":" + breakpoint.line,
+ lineNumber: breakpoint.line,
+ lineText: breakpoint.text,
+ truncatedLineText: trim(breakpoint.text, MAX_LINE_TEXT_LENGTH, "end")
+ });
+ }
+ }
+
+ return breakpoints;
+}
+
+function getAllSources(dbg) {
+ if (!dbg) {
+ return [];
+ }
+
+ let items = dbg._view.Sources.items;
+ return items
+ .filter(item => !!item.attachment.source.url)
+ .map(item => ({
+ name: item.attachment.source.url,
+ value: item.attachment.source.actor
+ }));
+}
+
+/**
+ * 'break' command
+ */
+exports.items.push({
+ name: "break",
+ description: gcli.lookup("breakDesc"),
+ manual: gcli.lookup("breakManual")
+});
+
+/**
+ * 'break list' command
+ */
+exports.items.push({
+ name: "break list",
+ description: gcli.lookup("breaklistDesc"),
+ returnType: "breakpoints",
+ exec: function(args, context) {
+ let dbg = getPanel(context, "jsdebugger", { ensureOpened: true });
+ return dbg.then(getAllBreakpoints);
+ }
+});
+
+exports.items.push({
+ item: "converter",
+ from: "breakpoints",
+ to: "view",
+ exec: function(breakpoints, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (dbg && breakpoints.length) {
+ return context.createView({
+ html: breakListHtml,
+ data: {
+ breakpoints: breakpoints,
+ onclick: context.update,
+ ondblclick: context.updateExec
+ }
+ });
+ } else {
+ return context.createView({
+ html: "<p>${message}</p>",
+ data: { message: gcli.lookup("breaklistNone") }
+ });
+ }
+ }
+});
+
+var breakListHtml = "" +
+ "<table>" +
+ " <thead>" +
+ " <th>Source</th>" +
+ " <th>Line</th>" +
+ " <th>Actions</th>" +
+ " </thead>" +
+ " <tbody>" +
+ " <tr foreach='breakpoint in ${breakpoints}'>" +
+ " <td class='gcli-breakpoint-label'>${breakpoint.label}</td>" +
+ " <td class='gcli-breakpoint-lineText'>" +
+ " ${breakpoint.truncatedLineText}" +
+ " </td>" +
+ " <td>" +
+ " <span class='gcli-out-shortcut'" +
+ " data-command='break del ${breakpoint.label}'" +
+ " onclick='${onclick}'" +
+ " ondblclick='${ondblclick}'>" +
+ " " + gcli.lookup("breaklistOutRemove") + "</span>" +
+ " </td>" +
+ " </tr>" +
+ " </tbody>" +
+ "</table>" +
+ "";
+
+var MAX_LINE_TEXT_LENGTH = 30;
+var MAX_LABEL_LENGTH = 20;
+
+/**
+ * 'break add' command
+ */
+exports.items.push({
+ name: "break add",
+ description: gcli.lookup("breakaddDesc"),
+ manual: gcli.lookup("breakaddManual")
+});
+
+/**
+ * 'break add line' command
+ */
+exports.items.push({
+ name: "break add line",
+ description: gcli.lookup("breakaddlineDesc"),
+ params: [
+ {
+ name: "file",
+ type: {
+ name: "selection",
+ lookup: function(context) {
+ return getAllSources(getPanel(context, "jsdebugger"));
+ }
+ },
+ description: gcli.lookup("breakaddlineFileDesc")
+ },
+ {
+ name: "line",
+ type: { name: "number", min: 1, step: 10 },
+ description: gcli.lookup("breakaddlineLineDesc")
+ }
+ ],
+ returnType: "string",
+ exec: function(args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return gcli.lookup("debuggerStopped");
+ }
+
+ let deferred = context.defer();
+ let item = dbg._view.Sources.getItemForAttachment(a => {
+ return a.source && a.source.actor === args.file;
+ })
+ let position = { actor: item.value, line: args.line };
+
+ dbg.addBreakpoint(position).then(() => {
+ deferred.resolve(gcli.lookup("breakaddAdded"));
+ }, aError => {
+ deferred.resolve(gcli.lookupFormat("breakaddFailed", [aError]));
+ });
+
+ return deferred.promise;
+ }
+});
+
+/**
+ * 'break del' command
+ */
+exports.items.push({
+ name: "break del",
+ description: gcli.lookup("breakdelDesc"),
+ params: [
+ {
+ name: "breakpoint",
+ type: {
+ name: "selection",
+ lookup: function(context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return [];
+ }
+ return getAllBreakpoints(dbg).map(breakpoint => ({
+ name: breakpoint.label,
+ value: breakpoint,
+ description: breakpoint.truncatedLineText
+ }));
+ }
+ },
+ description: gcli.lookup("breakdelBreakidDesc")
+ }
+ ],
+ returnType: "string",
+ exec: function(args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return gcli.lookup("debuggerStopped");
+ }
+
+ let source = dbg._view.Sources.getItemForAttachment(a => {
+ return a.source && a.source.url === args.breakpoint.url
+ });
+
+ let deferred = context.defer();
+ let position = { actor: source.attachment.source.actor,
+ line: args.breakpoint.lineNumber };
+
+ dbg.removeBreakpoint(position).then(() => {
+ deferred.resolve(gcli.lookup("breakdelRemoved"));
+ }, () => {
+ deferred.resolve(gcli.lookup("breakNotFound"));
+ });
+
+ return deferred.promise;
+ }
+});
+
+/**
+ * 'dbg' command
+ */
+exports.items.push({
+ name: "dbg",
+ description: gcli.lookup("dbgDesc"),
+ manual: gcli.lookup("dbgManual")
+});
+
+/**
+ * 'dbg open' command
+ */
+exports.items.push({
+ name: "dbg open",
+ description: gcli.lookup("dbgOpen"),
+ params: [],
+ exec: function(args, context) {
+ let target = context.environment.target;
+ return gDevTools.showToolbox(target, "jsdebugger").then(() => null);
+ }
+});
+
+/**
+ * 'dbg close' command
+ */
+exports.items.push({
+ name: "dbg close",
+ description: gcli.lookup("dbgClose"),
+ params: [],
+ exec: function(args, context) {
+ if (!getPanel(context, "jsdebugger")) {
+ return;
+ }
+ let target = context.environment.target;
+ return gDevTools.closeToolbox(target).then(() => null);
+ }
+});
+
+/**
+ * 'dbg interrupt' command
+ */
+exports.items.push({
+ name: "dbg interrupt",
+ description: gcli.lookup("dbgInterrupt"),
+ params: [],
+ exec: function(args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return gcli.lookup("debuggerStopped");
+ }
+
+ let controller = dbg._controller;
+ let thread = controller.activeThread;
+ if (!thread.paused) {
+ thread.interrupt();
+ }
+ }
+});
+
+/**
+ * 'dbg continue' command
+ */
+exports.items.push({
+ name: "dbg continue",
+ description: gcli.lookup("dbgContinue"),
+ params: [],
+ exec: function(args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return gcli.lookup("debuggerStopped");
+ }
+
+ let controller = dbg._controller;
+ let thread = controller.activeThread;
+ if (thread.paused) {
+ thread.resume();
+ }
+ }
+});
+
+/**
+ * 'dbg step' command
+ */
+exports.items.push({
+ name: "dbg step",
+ description: gcli.lookup("dbgStepDesc"),
+ manual: gcli.lookup("dbgStepManual")
+});
+
+/**
+ * 'dbg step over' command
+ */
+exports.items.push({
+ name: "dbg step over",
+ description: gcli.lookup("dbgStepOverDesc"),
+ params: [],
+ exec: function(args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return gcli.lookup("debuggerStopped");
+ }
+
+ let controller = dbg._controller;
+ let thread = controller.activeThread;
+ if (thread.paused) {
+ thread.stepOver();
+ }
+ }
+});
+
+/**
+ * 'dbg step in' command
+ */
+exports.items.push({
+ name: 'dbg step in',
+ description: gcli.lookup("dbgStepInDesc"),
+ params: [],
+ exec: function(args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return gcli.lookup("debuggerStopped");
+ }
+
+ let controller = dbg._controller;
+ let thread = controller.activeThread;
+ if (thread.paused) {
+ thread.stepIn();
+ }
+ }
+});
+
+/**
+ * 'dbg step over' command
+ */
+exports.items.push({
+ name: 'dbg step out',
+ description: gcli.lookup("dbgStepOutDesc"),
+ params: [],
+ exec: function(args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return gcli.lookup("debuggerStopped");
+ }
+
+ let controller = dbg._controller;
+ let thread = controller.activeThread;
+ if (thread.paused) {
+ thread.stepOut();
+ }
+ }
+});
+
+/**
+ * 'dbg list' command
+ */
+exports.items.push({
+ name: "dbg list",
+ description: gcli.lookup("dbgListSourcesDesc"),
+ params: [],
+ returnType: "dom",
+ exec: function(args, context) {
+ let dbg = getPanel(context, "jsdebugger");
+ if (!dbg) {
+ return gcli.lookup("debuggerClosed");
+ }
+
+ let sources = getAllSources(dbg);
+ let doc = context.environment.chromeDocument;
+ let div = createXHTMLElement(doc, "div");
+ let ol = createXHTMLElement(doc, "ol");
+
+ sources.forEach(source => {
+ let li = createXHTMLElement(doc, "li");
+ li.textContent = source.name;
+ ol.appendChild(li);
+ });
+ div.appendChild(ol);
+
+ return div;
+ }
+});
+
+/**
+ * Define the 'dbg blackbox' and 'dbg unblackbox' commands.
+ */
+[
+ {
+ name: "blackbox",
+ clientMethod: "blackBox",
+ l10nPrefix: "dbgBlackBox"
+ },
+ {
+ name: "unblackbox",
+ clientMethod: "unblackBox",
+ l10nPrefix: "dbgUnBlackBox"
+ }
+].forEach(function(cmd) {
+ const lookup = function(id) {
+ return gcli.lookup(cmd.l10nPrefix + id);
+ };
+
+ exports.items.push({
+ name: "dbg " + cmd.name,
+ description: lookup("Desc"),
+ params: [
+ {
+ name: "source",
+ type: {
+ name: "selection",
+ lookup: function(context) {
+ return getAllSources(getPanel(context, "jsdebugger"));
+ }
+ },
+ description: lookup("SourceDesc"),
+ defaultValue: null
+ },
+ {
+ name: "glob",
+ type: "string",
+ description: lookup("GlobDesc"),
+ defaultValue: null
+ },
+ {
+ name: "invert",
+ type: "boolean",
+ description: lookup("InvertDesc")
+ }
+ ],
+ returnType: "dom",
+ exec: function(args, context) {
+ const dbg = getPanel(context, "jsdebugger");
+ const doc = context.environment.chromeDocument;
+ if (!dbg) {
+ throw new Error(gcli.lookup("debuggerClosed"));
+ }
+
+ const { promise, resolve, reject } = context.defer();
+ const { activeThread } = dbg._controller;
+ const globRegExp = args.glob ? globToRegExp(args.glob) : null;
+
+ // Filter the sources down to those that we will need to black box.
+
+ function shouldBlackBox(source) {
+ var value = globRegExp && globRegExp.test(source.url)
+ || args.source && source.actor == args.source;
+ return args.invert ? !value : value;
+ }
+
+ const toBlackBox = [s.attachment.source
+ for (s of dbg._view.Sources.items)
+ if (shouldBlackBox(s.attachment.source))];
+
+ // If we aren't black boxing any sources, bail out now.
+
+ if (toBlackBox.length === 0) {
+ const empty = createXHTMLElement(doc, "div");
+ empty.textContent = lookup("EmptyDesc");
+ return void resolve(empty);
+ }
+
+ // Send the black box request to each source we are black boxing. As we
+ // get responses, accumulate the results in `blackBoxed`.
+
+ const blackBoxed = [];
+
+ for (let source of toBlackBox) {
+ activeThread.source(source)[cmd.clientMethod](function({ error }) {
+ if (error) {
+ blackBoxed.push(lookup("ErrorDesc") + " " + source.url);
+ } else {
+ blackBoxed.push(source.url);
+ }
+
+ if (toBlackBox.length === blackBoxed.length) {
+ displayResults();
+ }
+ });
+ }
+
+ // List the results for the user.
+
+ function displayResults() {
+ const results = doc.createElement("div");
+ results.textContent = lookup("NonEmptyDesc");
+
+ const list = createXHTMLElement(doc, "ul");
+ results.appendChild(list);
+
+ for (let result of blackBoxed) {
+ const item = createXHTMLElement(doc, "li");
+ item.textContent = result;
+ list.appendChild(item);
+ }
+ resolve(results);
+ }
+
+ return promise;
+ }
+ });
+});
+
+/**
+ * A helper to create xhtml namespaced elements.
+ */
+function createXHTMLElement(document, tagname) {
+ return document.createElementNS("http://www.w3.org/1999/xhtml", tagname);
+}
+
+/**
+ * A helper to go from a command context to a debugger panel.
+ */
+function getPanel(context, id, options = {}) {
+ if (!context) {
+ return undefined;
+ }
+
+ let target = context.environment.target;
+
+ if (options.ensureOpened) {
+ return gDevTools.showToolbox(target, id).then(toolbox => {
+ return toolbox.getPanel(id);
+ });
+ } else {
+ let toolbox = gDevTools.getToolbox(target);
+ if (toolbox) {
+ return toolbox.getPanel(id);
+ } else {
+ return undefined;
+ }
+ }
+}
+
+/**
+ * Converts a glob to a regular expression.
+ */
+function globToRegExp(glob) {
+ const reStr = glob
+ // Escape existing regular expression syntax.
+ .replace(/\\/g, "\\\\")
+ .replace(/\//g, "\\/")
+ .replace(/\^/g, "\\^")
+ .replace(/\$/g, "\\$")
+ .replace(/\+/g, "\\+")
+ .replace(/\?/g, "\\?")
+ .replace(/\./g, "\\.")
+ .replace(/\(/g, "\\(")
+ .replace(/\)/g, "\\)")
+ .replace(/\=/g, "\\=")
+ .replace(/\!/g, "\\!")
+ .replace(/\|/g, "\\|")
+ .replace(/\{/g, "\\{")
+ .replace(/\}/g, "\\}")
+ .replace(/\,/g, "\\,")
+ .replace(/\[/g, "\\[")
+ .replace(/\]/g, "\\]")
+ .replace(/\-/g, "\\-")
+ // Turn * into the match everything wildcard.
+ .replace(/\*/g, ".*")
+ return new RegExp("^" + reStr + "$");
+}
diff --git a/toolkit/devtools/debugger/debugger-controller.js b/toolkit/devtools/debugger/debugger-controller.js
new file mode 100644
index 000000000..531d7df1a
--- /dev/null
+++ b/toolkit/devtools/debugger/debugger-controller.js
@@ -0,0 +1,2427 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
+const NEW_SOURCE_IGNORED_URLS = ["debugger eval code", "XStringBundle"];
+const NEW_SOURCE_DISPLAY_DELAY = 200; // ms
+const FETCH_SOURCE_RESPONSE_DELAY = 200; // ms
+const FETCH_EVENT_LISTENERS_DELAY = 200; // ms
+const FRAME_STEP_CLEAR_DELAY = 100; // ms
+const CALL_STACK_PAGE_SIZE = 25; // frames
+
+// The panel's window global is an EventEmitter firing the following events:
+const EVENTS = {
+ // When the debugger's source editor instance finishes loading or unloading.
+ EDITOR_LOADED: "Debugger:EditorLoaded",
+ EDITOR_UNLOADED: "Debugger:EditorUnoaded",
+
+ // When new sources are received from the debugger server.
+ NEW_SOURCE: "Debugger:NewSource",
+ SOURCES_ADDED: "Debugger:SourcesAdded",
+
+ // When a source is shown in the source editor.
+ SOURCE_SHOWN: "Debugger:EditorSourceShown",
+ SOURCE_ERROR_SHOWN: "Debugger:EditorSourceErrorShown",
+
+ // When the editor has shown a source and set the line / column position
+ EDITOR_LOCATION_SET: "Debugger:EditorLocationSet",
+
+ // When scopes, variables, properties and watch expressions are fetched and
+ // displayed in the variables view.
+ FETCHED_SCOPES: "Debugger:FetchedScopes",
+ FETCHED_VARIABLES: "Debugger:FetchedVariables",
+ FETCHED_PROPERTIES: "Debugger:FetchedProperties",
+ FETCHED_BUBBLE_PROPERTIES: "Debugger:FetchedBubbleProperties",
+ FETCHED_WATCH_EXPRESSIONS: "Debugger:FetchedWatchExpressions",
+
+ // When a breakpoint has been added or removed on the debugger server.
+ BREAKPOINT_ADDED: "Debugger:BreakpointAdded",
+ BREAKPOINT_REMOVED: "Debugger:BreakpointRemoved",
+
+ // When a breakpoint has been shown or hidden in the source editor
+ // or the pane.
+ BREAKPOINT_SHOWN_IN_EDITOR: "Debugger:BreakpointShownInEditor",
+ BREAKPOINT_SHOWN_IN_PANE: "Debugger:BreakpointShownInPane",
+ BREAKPOINT_HIDDEN_IN_EDITOR: "Debugger:BreakpointHiddenInEditor",
+ BREAKPOINT_HIDDEN_IN_PANE: "Debugger:BreakpointHiddenInPane",
+
+ // When a conditional breakpoint's popup is showing or hiding.
+ CONDITIONAL_BREAKPOINT_POPUP_SHOWING: "Debugger:ConditionalBreakpointPopupShowing",
+ CONDITIONAL_BREAKPOINT_POPUP_HIDING: "Debugger:ConditionalBreakpointPopupHiding",
+
+ // When event listeners are fetched or event breakpoints are updated.
+ EVENT_LISTENERS_FETCHED: "Debugger:EventListenersFetched",
+ EVENT_BREAKPOINTS_UPDATED: "Debugger:EventBreakpointsUpdated",
+
+ // When a file search was performed.
+ FILE_SEARCH_MATCH_FOUND: "Debugger:FileSearch:MatchFound",
+ FILE_SEARCH_MATCH_NOT_FOUND: "Debugger:FileSearch:MatchNotFound",
+
+ // When a function search was performed.
+ FUNCTION_SEARCH_MATCH_FOUND: "Debugger:FunctionSearch:MatchFound",
+ FUNCTION_SEARCH_MATCH_NOT_FOUND: "Debugger:FunctionSearch:MatchNotFound",
+
+ // When a global text search was performed.
+ GLOBAL_SEARCH_MATCH_FOUND: "Debugger:GlobalSearch:MatchFound",
+ GLOBAL_SEARCH_MATCH_NOT_FOUND: "Debugger:GlobalSearch:MatchNotFound",
+
+ // After the the StackFrames object has been filled with frames
+ AFTER_FRAMES_REFILLED: "Debugger:AfterFramesRefilled",
+
+ // After the stackframes are cleared and debugger won't pause anymore.
+ AFTER_FRAMES_CLEARED: "Debugger:AfterFramesCleared",
+
+ // When the options popup is showing or hiding.
+ OPTIONS_POPUP_SHOWING: "Debugger:OptionsPopupShowing",
+ OPTIONS_POPUP_HIDDEN: "Debugger:OptionsPopupHidden",
+
+ // When the widgets layout has been changed.
+ LAYOUT_CHANGED: "Debugger:LayoutChanged"
+};
+
+// Descriptions for what a stack frame represents after the debugger pauses.
+const FRAME_TYPE = {
+ NORMAL: 0,
+ CONDITIONAL_BREAKPOINT_EVAL: 1,
+ WATCH_EXPRESSIONS_EVAL: 2,
+ PUBLIC_CLIENT_EVAL: 3
+};
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/devtools/event-emitter.js");
+Cu.import("resource:///modules/devtools/SimpleListWidget.jsm");
+Cu.import("resource:///modules/devtools/BreadcrumbsWidget.jsm");
+Cu.import("resource:///modules/devtools/SideMenuWidget.jsm");
+Cu.import("resource:///modules/devtools/VariablesView.jsm");
+Cu.import("resource:///modules/devtools/VariablesViewController.jsm");
+Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
+
+const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+const promise = require("devtools/toolkit/deprecated-sync-thenables");
+const Editor = require("devtools/sourceeditor/editor");
+const DebuggerEditor = require("devtools/sourceeditor/debugger.js");
+const {Tooltip} = require("devtools/shared/widgets/Tooltip");
+const FastListWidget = require("devtools/shared/widgets/FastListWidget");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Parser",
+ "resource:///modules/devtools/Parser.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "devtools",
+ "resource://gre/modules/devtools/Loader.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DevToolsUtils",
+ "resource://gre/modules/devtools/DevToolsUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
+ "resource://gre/modules/ShortcutUtils.jsm");
+
+Object.defineProperty(this, "NetworkHelper", {
+ get: function() {
+ return devtools.require("devtools/toolkit/webconsole/network-helper");
+ },
+ configurable: true,
+ enumerable: true
+});
+
+/**
+ * Object defining the debugger controller components.
+ */
+let DebuggerController = {
+ /**
+ * Initializes the debugger controller.
+ */
+ initialize: function() {
+ dumpn("Initializing the DebuggerController");
+
+ this.startupDebugger = this.startupDebugger.bind(this);
+ this.shutdownDebugger = this.shutdownDebugger.bind(this);
+ this._onTabNavigated = this._onTabNavigated.bind(this);
+ this._onTabDetached = this._onTabDetached.bind(this);
+ },
+
+ /**
+ * Initializes the view.
+ *
+ * @return object
+ * A promise that is resolved when the debugger finishes startup.
+ */
+ startupDebugger: Task.async(function*() {
+ if (this._startup) {
+ return;
+ }
+
+ yield DebuggerView.initialize();
+ this._startup = true;
+ }),
+
+ /**
+ * Destroys the view and disconnects the debugger client from the server.
+ *
+ * @return object
+ * A promise that is resolved when the debugger finishes shutdown.
+ */
+ shutdownDebugger: Task.async(function*() {
+ if (this._shutdown) {
+ return;
+ }
+
+ yield DebuggerView.destroy();
+ this.SourceScripts.disconnect();
+ this.StackFrames.disconnect();
+ this.ThreadState.disconnect();
+ this.Tracer.disconnect();
+ this.disconnect();
+
+ this._shutdown = true;
+ }),
+
+ /**
+ * Initiates remote debugging based on the current target, wiring event
+ * handlers as necessary.
+ *
+ * @return object
+ * A promise that is resolved when the debugger finishes connecting.
+ */
+ connect: Task.async(function*() {
+ if (this._connected) {
+ return;
+ }
+
+ let target = this._target;
+ let { client, form: { chromeDebugger, traceActor, actor } } = target;
+ target.on("close", this._onTabDetached);
+ target.on("navigate", this._onTabNavigated);
+ target.on("will-navigate", this._onTabNavigated);
+ this.client = client;
+
+ if (target.isAddon) {
+ yield this._startAddonDebugging(actor);
+ } else if (target.chrome) {
+ yield this._startChromeDebugging(chromeDebugger);
+ } else {
+ yield this._startDebuggingTab();
+
+ if (Prefs.tracerEnabled && traceActor) {
+ yield this._startTracingTab(traceActor);
+ }
+ }
+
+ this._hideUnsupportedFeatures();
+ }),
+
+ /**
+ * Disconnects the debugger client and removes event handlers as necessary.
+ */
+ disconnect: function() {
+ // Return early if the client didn't even have a chance to instantiate.
+ if (!this.client) {
+ return;
+ }
+
+ this._connected = false;
+ this.client = null;
+ this.activeThread = null;
+ },
+
+ _hideUnsupportedFeatures: function() {
+ if (this.client.mainRoot.traits.noPrettyPrinting) {
+ DebuggerView.Sources.hidePrettyPrinting();
+ }
+
+ if (this.client.mainRoot.traits.noBlackBoxing) {
+ DebuggerView.Sources.hideBlackBoxing();
+ }
+ },
+
+ /**
+ * Called for each location change in the debugged tab.
+ *
+ * @param string aType
+ * Packet type.
+ * @param object aPacket
+ * Packet received from the server.
+ */
+ _onTabNavigated: function(aType, aPacket) {
+ switch (aType) {
+ case "will-navigate": {
+ // Reset UI.
+ DebuggerView.handleTabNavigation();
+
+ // Discard all the cached sources *before* the target starts navigating.
+ // Sources may be fetched during navigation, in which case we don't
+ // want to hang on to the old source contents.
+ DebuggerController.SourceScripts.clearCache();
+ DebuggerController.Parser.clearCache();
+ SourceUtils.clearCache();
+
+ // Prevent performing any actions that were scheduled before navigation.
+ clearNamedTimeout("new-source");
+ clearNamedTimeout("event-breakpoints-update");
+ clearNamedTimeout("event-listeners-fetch");
+ break;
+ }
+ case "navigate": {
+ this.ThreadState.handleTabNavigation();
+ this.StackFrames.handleTabNavigation();
+ this.SourceScripts.handleTabNavigation();
+ break;
+ }
+ }
+ },
+
+ /**
+ * Called when the debugged tab is closed.
+ */
+ _onTabDetached: function() {
+ this.shutdownDebugger();
+ },
+
+ /**
+ * Warn if resuming execution produced a wrongOrder error.
+ */
+ _ensureResumptionOrder: function(aResponse) {
+ if (aResponse.error == "wrongOrder") {
+ DebuggerView.Toolbar.showResumeWarning(aResponse.lastPausedUrl);
+ }
+ },
+
+ /**
+ * Sets up a debugging session.
+ *
+ * @return object
+ * A promise resolved once the client attaches to the active thread.
+ */
+ _startDebuggingTab: function() {
+ let deferred = promise.defer();
+ let threadOptions = {
+ useSourceMaps: Prefs.sourceMapsEnabled,
+ autoBlackBox: Prefs.autoBlackBox
+ };
+
+ this._target.activeTab.attachThread(threadOptions, (aResponse, aThreadClient) => {
+ if (!aThreadClient) {
+ deferred.reject(new Error("Couldn't attach to thread: " + aResponse.error));
+ return;
+ }
+ this.activeThread = aThreadClient;
+ this.ThreadState.connect();
+ this.StackFrames.connect();
+ this.SourceScripts.connect();
+
+ if (aThreadClient.paused) {
+ aThreadClient.resume(this._ensureResumptionOrder);
+ }
+
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+ },
+
+ /**
+ * Sets up an addon debugging session.
+ *
+ * @param object aAddonActor
+ * The actor for the addon that is being debugged.
+ * @return object
+ * A promise resolved once the client attaches to the active thread.
+ */
+ _startAddonDebugging: function(aAddonActor) {
+ let deferred = promise.defer();
+
+ this.client.attachAddon(aAddonActor, aResponse => {
+ this._startChromeDebugging(aResponse.threadActor).then(deferred.resolve);
+ });
+
+ return deferred.promise;
+ },
+
+ /**
+ * Sets up a chrome debugging session.
+ *
+ * @param object aChromeDebugger
+ * The remote protocol grip of the chrome debugger.
+ * @return object
+ * A promise resolved once the client attaches to the active thread.
+ */
+ _startChromeDebugging: function(aChromeDebugger) {
+ let deferred = promise.defer();
+ let threadOptions = {
+ useSourceMaps: Prefs.sourceMapsEnabled,
+ autoBlackBox: Prefs.autoBlackBox
+ };
+
+ this.client.attachThread(aChromeDebugger, (aResponse, aThreadClient) => {
+ if (!aThreadClient) {
+ deferred.reject(new Error("Couldn't attach to thread: " + aResponse.error));
+ return;
+ }
+ this.activeThread = aThreadClient;
+ this.ThreadState.connect();
+ this.StackFrames.connect();
+ this.SourceScripts.connect();
+
+ if (aThreadClient.paused) {
+ aThreadClient.resume(this._ensureResumptionOrder);
+ }
+
+ deferred.resolve();
+ }, threadOptions);
+
+ return deferred.promise;
+ },
+
+ /**
+ * Sets up an execution tracing session.
+ *
+ * @param object aTraceActor
+ * The remote protocol grip of the trace actor.
+ * @return object
+ * A promise resolved once the client attaches to the tracer.
+ */
+ _startTracingTab: function(aTraceActor) {
+ let deferred = promise.defer();
+
+ this.client.attachTracer(aTraceActor, (response, traceClient) => {
+ if (!traceClient) {
+ deferred.reject(new Error("Failed to attach to tracing actor."));
+ return;
+ }
+ this.traceClient = traceClient;
+ this.Tracer.connect();
+
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+ },
+
+ /**
+ * Detach and reattach to the thread actor with useSourceMaps true, blow
+ * away old sources and get them again.
+ */
+ reconfigureThread: function({ useSourceMaps, autoBlackBox }) {
+ this.activeThread.reconfigure({
+ useSourceMaps: useSourceMaps,
+ autoBlackBox: autoBlackBox
+ }, aResponse => {
+ if (aResponse.error) {
+ let msg = "Couldn't reconfigure thread: " + aResponse.message;
+ Cu.reportError(msg);
+ dumpn(msg);
+ return;
+ }
+
+ // Reset the view and fetch all the sources again.
+ DebuggerView.handleTabNavigation();
+ this.SourceScripts.handleTabNavigation();
+
+ // Update the stack frame list.
+ if (this.activeThread.paused) {
+ this.activeThread._clearFrames();
+ this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
+ }
+ });
+ },
+
+ _startup: false,
+ _shutdown: false,
+ _connected: false,
+ client: null,
+ activeThread: null
+};
+
+/**
+ * ThreadState keeps the UI up to date with the state of the
+ * thread (paused/attached/etc.).
+ */
+function ThreadState() {
+ this._update = this._update.bind(this);
+ this.interruptedByResumeButton = false;
+}
+
+ThreadState.prototype = {
+ get activeThread() DebuggerController.activeThread,
+
+ /**
+ * Connect to the current thread client.
+ */
+ connect: function() {
+ dumpn("ThreadState is connecting...");
+ this.activeThread.addListener("paused", this._update);
+ this.activeThread.addListener("resumed", this._update);
+ this.activeThread.pauseOnExceptions(Prefs.pauseOnExceptions,
+ Prefs.ignoreCaughtExceptions);
+ },
+
+ /**
+ * Disconnect from the client.
+ */
+ disconnect: function() {
+ if (!this.activeThread) {
+ return;
+ }
+ dumpn("ThreadState is disconnecting...");
+ this.activeThread.removeListener("paused", this._update);
+ this.activeThread.removeListener("resumed", this._update);
+ },
+
+ /**
+ * Handles any initialization on a tab navigation event issued by the client.
+ */
+ handleTabNavigation: function() {
+ if (!this.activeThread) {
+ return;
+ }
+ dumpn("Handling tab navigation in the ThreadState");
+ this._update();
+ },
+
+ /**
+ * Update the UI after a thread state change.
+ */
+ _update: function(aEvent, aPacket) {
+ // Ignore "interrupted" events, to avoid UI flicker. These are generated
+ // by the slow script dialog and internal events such as setting
+ // breakpoints. Pressing the resume button does need to be shown, though.
+ if (aEvent == "paused" &&
+ aPacket.why.type == "interrupted" &&
+ !this.interruptedByResumeButton) {
+ return;
+ }
+
+ this.interruptedByResumeButton = false;
+ DebuggerView.Toolbar.toggleResumeButtonState(this.activeThread.state);
+
+ if (gTarget && (aEvent == "paused" || aEvent == "resumed")) {
+ gTarget.emit("thread-" + aEvent);
+ }
+ }
+};
+
+/**
+ * Keeps the stack frame list up-to-date, using the thread client's
+ * stack frame cache.
+ */
+function StackFrames() {
+ this._onPaused = this._onPaused.bind(this);
+ this._onResumed = this._onResumed.bind(this);
+ this._onFrames = this._onFrames.bind(this);
+ this._onFramesCleared = this._onFramesCleared.bind(this);
+ this._onBlackBoxChange = this._onBlackBoxChange.bind(this);
+ this._onPrettyPrintChange = this._onPrettyPrintChange.bind(this);
+ this._afterFramesCleared = this._afterFramesCleared.bind(this);
+ this.evaluate = this.evaluate.bind(this);
+}
+
+StackFrames.prototype = {
+ get activeThread() DebuggerController.activeThread,
+ currentFrameDepth: -1,
+ _currentFrameDescription: FRAME_TYPE.NORMAL,
+ _syncedWatchExpressions: null,
+ _currentWatchExpressions: null,
+ _currentBreakpointLocation: null,
+ _currentEvaluation: null,
+ _currentException: null,
+ _currentReturnedValue: null,
+
+ /**
+ * Connect to the current thread client.
+ */
+ connect: function() {
+ dumpn("StackFrames is connecting...");
+ this.activeThread.addListener("paused", this._onPaused);
+ this.activeThread.addListener("resumed", this._onResumed);
+ this.activeThread.addListener("framesadded", this._onFrames);
+ this.activeThread.addListener("framescleared", this._onFramesCleared);
+ this.activeThread.addListener("blackboxchange", this._onBlackBoxChange);
+ this.activeThread.addListener("prettyprintchange", this._onPrettyPrintChange);
+ this.handleTabNavigation();
+ },
+
+ /**
+ * Disconnect from the client.
+ */
+ disconnect: function() {
+ if (!this.activeThread) {
+ return;
+ }
+ dumpn("StackFrames is disconnecting...");
+ this.activeThread.removeListener("paused", this._onPaused);
+ this.activeThread.removeListener("resumed", this._onResumed);
+ this.activeThread.removeListener("framesadded", this._onFrames);
+ this.activeThread.removeListener("framescleared", this._onFramesCleared);
+ this.activeThread.removeListener("blackboxchange", this._onBlackBoxChange);
+ this.activeThread.removeListener("prettyprintchange", this._onPrettyPrintChange);
+ clearNamedTimeout("frames-cleared");
+ },
+
+ /**
+ * Handles any initialization on a tab navigation event issued by the client.
+ */
+ handleTabNavigation: function() {
+ dumpn("Handling tab navigation in the StackFrames");
+ // Nothing to do here yet.
+ },
+
+ /**
+ * Handler for the thread client's paused notification.
+ *
+ * @param string aEvent
+ * The name of the notification ("paused" in this case).
+ * @param object aPacket
+ * The response packet.
+ */
+ _onPaused: function(aEvent, aPacket) {
+ switch (aPacket.why.type) {
+ // If paused by a breakpoint, store the breakpoint location.
+ case "breakpoint":
+ this._currentBreakpointLocation = aPacket.frame.where;
+ break;
+ // If paused by a client evaluation, store the evaluated value.
+ case "clientEvaluated":
+ this._currentEvaluation = aPacket.why.frameFinished;
+ break;
+ // If paused by an exception, store the exception value.
+ case "exception":
+ this._currentException = aPacket.why.exception;
+ break;
+ // If paused while stepping out of a frame, store the returned value or
+ // thrown exception.
+ case "resumeLimit":
+ if (!aPacket.why.frameFinished) {
+ break;
+ } else if (aPacket.why.frameFinished.throw) {
+ this._currentException = aPacket.why.frameFinished.throw;
+ } else if (aPacket.why.frameFinished.return) {
+ this._currentReturnedValue = aPacket.why.frameFinished.return;
+ }
+ break;
+ // If paused by an explicit interrupt, which are generated by the slow
+ // script dialog and internal events such as setting breakpoints, ignore
+ // the event to avoid UI flicker.
+ case "interrupted":
+ return;
+ }
+
+ this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE);
+ DebuggerView.editor.focus();
+ },
+
+ /**
+ * Handler for the thread client's resumed notification.
+ */
+ _onResumed: function() {
+ // Prepare the watch expression evaluation string for the next pause.
+ if (this._currentFrameDescription != FRAME_TYPE.WATCH_EXPRESSIONS_EVAL) {
+ this._currentWatchExpressions = this._syncedWatchExpressions;
+ }
+ },
+
+ /**
+ * Handler for the thread client's framesadded notification.
+ */
+ _onFrames: Task.async(function*() {
+ // Ignore useless notifications.
+ if (!this.activeThread || !this.activeThread.cachedFrames.length) {
+ return;
+ }
+ if (this._currentFrameDescription != FRAME_TYPE.NORMAL &&
+ this._currentFrameDescription != FRAME_TYPE.PUBLIC_CLIENT_EVAL) {
+ return;
+ }
+
+ // TODO: remove all of this deprecated code: Bug 990137.
+ yield this._handleConditionalBreakpoint();
+
+ // TODO: handle all of this server-side: Bug 832470, comment 14.
+ yield this._handleWatchExpressions();
+
+ // Make sure the debugger view panes are visible, then refill the frames.
+ DebuggerView.showInstrumentsPane();
+ this._refillFrames();
+
+ // No additional processing is necessary for this stack frame.
+ if (this._currentFrameDescription != FRAME_TYPE.NORMAL) {
+ this._currentFrameDescription = FRAME_TYPE.NORMAL;
+ }
+ }),
+
+ /**
+ * Fill the StackFrames view with the frames we have in the cache, compressing
+ * frames which have black boxed sources into single frames.
+ */
+ _refillFrames: function() {
+ // Make sure all the previous stackframes are removed before re-adding them.
+ DebuggerView.StackFrames.empty();
+
+ for (let frame of this.activeThread.cachedFrames) {
+ let { depth, source, where: { line } } = frame;
+
+ let isBlackBoxed = source ? this.activeThread.source(source).isBlackBoxed : false;
+ let location = NetworkHelper.convertToUnicode(unescape(source.url || source.introductionUrl));
+ let title = StackFrameUtils.getFrameTitle(frame);
+ DebuggerView.StackFrames.addFrame(title, location, line, depth, isBlackBoxed);
+ }
+
+ DebuggerView.StackFrames.selectedDepth = Math.max(this.currentFrameDepth, 0);
+ DebuggerView.StackFrames.dirty = this.activeThread.moreFrames;
+
+ window.emit(EVENTS.AFTER_FRAMES_REFILLED);
+ },
+
+ /**
+ * Handler for the thread client's framescleared notification.
+ */
+ _onFramesCleared: function() {
+ switch (this._currentFrameDescription) {
+ case FRAME_TYPE.NORMAL:
+ this._currentEvaluation = null;
+ this._currentException = null;
+ this._currentReturnedValue = null;
+ break;
+ case FRAME_TYPE.CONDITIONAL_BREAKPOINT_EVAL:
+ this._currentBreakpointLocation = null;
+ break;
+ case FRAME_TYPE.WATCH_EXPRESSIONS_EVAL:
+ this._currentWatchExpressions = null;
+ break;
+ }
+
+ // After each frame step (in, over, out), framescleared is fired, which
+ // forces the UI to be emptied and rebuilt on framesadded. Most of the times
+ // this is not necessary, and will result in a brief redraw flicker.
+ // To avoid it, invalidate the UI only after a short time if necessary.
+ setNamedTimeout("frames-cleared", FRAME_STEP_CLEAR_DELAY, this._afterFramesCleared);
+ },
+
+ /**
+ * Handler for the debugger's blackboxchange notification.
+ */
+ _onBlackBoxChange: function() {
+ if (this.activeThread.state == "paused") {
+ // Hack to avoid selecting the topmost frame after blackboxing a source.
+ this.currentFrameDepth = NaN;
+ this._refillFrames();
+ }
+ },
+
+ /**
+ * Handler for the debugger's prettyprintchange notification.
+ */
+ _onPrettyPrintChange: function() {
+ if (this.activeThread.state != "paused") {
+ return;
+ }
+ // Makes sure the selected source remains selected
+ // after the fillFrames is called.
+ const source = DebuggerView.Sources.selectedValue;
+
+ this.activeThread.fillFrames(CALL_STACK_PAGE_SIZE, () => {
+ DebuggerView.Sources.selectedValue = source;
+ });
+ },
+
+ /**
+ * Called soon after the thread client's framescleared notification.
+ */
+ _afterFramesCleared: function() {
+ // Ignore useless notifications.
+ if (this.activeThread.cachedFrames.length) {
+ return;
+ }
+ DebuggerView.editor.clearDebugLocation();
+ DebuggerView.StackFrames.empty();
+ DebuggerView.Sources.unhighlightBreakpoint();
+ DebuggerView.WatchExpressions.toggleContents(true);
+ DebuggerView.Variables.empty(0);
+
+ window.emit(EVENTS.AFTER_FRAMES_CLEARED);
+ },
+
+ /**
+ * Marks the stack frame at the specified depth as selected and updates the
+ * properties view with the stack frame's data.
+ *
+ * @param number aDepth
+ * The depth of the frame in the stack.
+ */
+ selectFrame: function(aDepth) {
+ // Make sure the frame at the specified depth exists first.
+ let frame = this.activeThread.cachedFrames[this.currentFrameDepth = aDepth];
+ if (!frame) {
+ return;
+ }
+
+ // Check if the frame does not represent the evaluation of debuggee code.
+ let { environment, where, source } = frame;
+ if (!environment) {
+ return;
+ }
+
+ // Don't change the editor's location if the execution was paused by a
+ // public client evaluation. This is useful for adding overlays on
+ // top of the editor, like a variable inspection popup.
+ let isClientEval = this._currentFrameDescription == FRAME_TYPE.PUBLIC_CLIENT_EVAL;
+ let isPopupShown = DebuggerView.VariableBubble.contentsShown();
+ if (!isClientEval && !isPopupShown) {
+ // Move the editor's caret to the proper url and line.
+ DebuggerView.setEditorLocation(source.actor, where.line);
+ } else {
+ // Highlight the line where the execution is paused in the editor.
+ DebuggerView.setEditorLocation(source.actor, where.line, { noCaret: true });
+ }
+
+ // Highlight the breakpoint at the line and column if it exists.
+ DebuggerView.Sources.highlightBreakpointAtCursor();
+
+ // Don't display the watch expressions textbox inputs in the pane.
+ DebuggerView.WatchExpressions.toggleContents(false);
+
+ // Start recording any added variables or properties in any scope and
+ // clear existing scopes to create each one dynamically.
+ DebuggerView.Variables.empty();
+
+ // If watch expressions evaluation results are available, create a scope
+ // to contain all the values.
+ if (this._syncedWatchExpressions && aDepth == 0) {
+ let label = L10N.getStr("watchExpressionsScopeLabel");
+ let scope = DebuggerView.Variables.addScope(label);
+
+ // Customize the scope for holding watch expressions evaluations.
+ scope.descriptorTooltip = false;
+ scope.contextMenuId = "debuggerWatchExpressionsContextMenu";
+ scope.separatorStr = L10N.getStr("watchExpressionsSeparatorLabel");
+ scope.switch = DebuggerView.WatchExpressions.switchExpression;
+ scope.delete = DebuggerView.WatchExpressions.deleteExpression;
+
+ // The evaluation hasn't thrown, so fetch and add the returned results.
+ this._fetchWatchExpressions(scope, this._currentEvaluation.return);
+
+ // The watch expressions scope is always automatically expanded.
+ scope.expand();
+ }
+
+ do {
+ // Create a scope to contain all the inspected variables in the
+ // current environment.
+ let label = StackFrameUtils.getScopeLabel(environment);
+ let scope = DebuggerView.Variables.addScope(label);
+ let innermost = environment == frame.environment;
+
+ // Handle special additions to the innermost scope.
+ if (innermost) {
+ this._insertScopeFrameReferences(scope, frame);
+ }
+
+ // Handle the expansion of the scope, lazily populating it with the
+ // variables in the current environment.
+ DebuggerView.Variables.controller.addExpander(scope, environment);
+
+ // The innermost scope is always automatically expanded, because it
+ // contains the variables in the current stack frame which are likely to
+ // be inspected. The previously expanded scopes are also reexpanded here.
+ if (innermost || DebuggerView.Variables.wasExpanded(scope)) {
+ scope.expand();
+ }
+ } while ((environment = environment.parent));
+
+ // Signal that scope environments have been shown.
+ window.emit(EVENTS.FETCHED_SCOPES);
+ },
+
+ /**
+ * Loads more stack frames from the debugger server cache.
+ */
+ addMoreFrames: function() {
+ this.activeThread.fillFrames(
+ this.activeThread.cachedFrames.length + CALL_STACK_PAGE_SIZE);
+ },
+
+ /**
+ * Evaluate an expression in the context of the selected frame.
+ *
+ * @param string aExpression
+ * The expression to evaluate.
+ * @param object aOptions [optional]
+ * Additional options for this client evaluation:
+ * - depth: the frame depth used for evaluation, 0 being the topmost.
+ * - meta: some meta-description for what this evaluation represents.
+ * @return object
+ * A promise that is resolved when the evaluation finishes,
+ * or rejected if there was no stack frame available or some
+ * other error occurred.
+ */
+ evaluate: function(aExpression, aOptions = {}) {
+ let depth = "depth" in aOptions ? aOptions.depth : this.currentFrameDepth;
+ let frame = this.activeThread.cachedFrames[depth];
+ if (frame == null) {
+ return promise.reject(new Error("No stack frame available."));
+ }
+
+ let deferred = promise.defer();
+
+ this.activeThread.addOneTimeListener("paused", (aEvent, aPacket) => {
+ let { type, frameFinished } = aPacket.why;
+ if (type == "clientEvaluated") {
+ deferred.resolve(frameFinished);
+ } else {
+ deferred.reject(new Error("Active thread paused unexpectedly."));
+ }
+ });
+
+ let meta = "meta" in aOptions ? aOptions.meta : FRAME_TYPE.PUBLIC_CLIENT_EVAL;
+ this._currentFrameDescription = meta;
+ this.activeThread.eval(frame.actor, aExpression);
+
+ return deferred.promise;
+ },
+
+ /**
+ * Add nodes for special frame references in the innermost scope.
+ *
+ * @param Scope aScope
+ * The scope where the references will be placed into.
+ * @param object aFrame
+ * The frame to get some references from.
+ */
+ _insertScopeFrameReferences: function(aScope, aFrame) {
+ // Add any thrown exception.
+ if (this._currentException) {
+ let excRef = aScope.addItem("<exception>", { value: this._currentException });
+ DebuggerView.Variables.controller.addExpander(excRef, this._currentException);
+ }
+ // Add any returned value.
+ if (this._currentReturnedValue) {
+ let retRef = aScope.addItem("<return>", { value: this._currentReturnedValue });
+ DebuggerView.Variables.controller.addExpander(retRef, this._currentReturnedValue);
+ }
+ // Add "this".
+ if (aFrame.this) {
+ let thisRef = aScope.addItem("this", { value: aFrame.this });
+ DebuggerView.Variables.controller.addExpander(thisRef, aFrame.this);
+ }
+ },
+
+ /**
+ * Handles conditional breakpoints when the debugger pauses and the
+ * stackframes are received.
+ *
+ * We moved conditional breakpoint handling to the server, but
+ * need to support it in the client for a while until most of the
+ * server code in production is updated with it.
+ * TODO: remove all of this deprecated code: Bug 990137.
+ *
+ * @return object
+ * A promise that is resolved after a potential breakpoint's
+ * conditional expression is evaluated. If there's no breakpoint
+ * where the debugger is paused, the promise is resolved immediately.
+ */
+ _handleConditionalBreakpoint: Task.async(function*() {
+ if (gClient.mainRoot.traits.conditionalBreakpoints) {
+ return;
+ }
+ let breakLocation = this._currentBreakpointLocation;
+ if (!breakLocation) {
+ return;
+ }
+
+ let breakpointPromise = DebuggerController.Breakpoints._getAdded(breakLocation);
+ if (!breakpointPromise) {
+ return;
+ }
+ let breakpointClient = yield breakpointPromise;
+ let conditionalExpression = breakpointClient.conditionalExpression;
+ if (!conditionalExpression) {
+ return;
+ }
+
+ // Evaluating the current breakpoint's conditional expression will
+ // cause the stack frames to be cleared and active thread to pause,
+ // sending a 'clientEvaluated' packed and adding the frames again.
+ let evaluationOptions = { depth: 0, meta: FRAME_TYPE.CONDITIONAL_BREAKPOINT_EVAL };
+ yield this.evaluate(conditionalExpression, evaluationOptions);
+ this._currentFrameDescription = FRAME_TYPE.NORMAL;
+
+ // If the breakpoint's conditional expression evaluation is falsy,
+ // automatically resume execution.
+ if (VariablesView.isFalsy({ value: this._currentEvaluation.return })) {
+ this.activeThread.resume(DebuggerController._ensureResumptionOrder);
+ }
+ }),
+
+ /**
+ * Handles watch expressions when the debugger pauses and the stackframes
+ * are received.
+ *
+ * @return object
+ * A promise that is resolved after the potential watch expressions
+ * are evaluated. If there are no watch expressions where the debugger
+ * is paused, the promise is resolved immediately.
+ */
+ _handleWatchExpressions: Task.async(function*() {
+ // Ignore useless notifications.
+ if (!this.activeThread || !this.activeThread.cachedFrames.length) {
+ return;
+ }
+
+ let watchExpressions = this._currentWatchExpressions;
+ if (!watchExpressions) {
+ return;
+ }
+
+ // Evaluation causes the stack frames to be cleared and active thread to
+ // pause, sending a 'clientEvaluated' packet and adding the frames again.
+ let evaluationOptions = { depth: 0, meta: FRAME_TYPE.WATCH_EXPRESSIONS_EVAL };
+ yield this.evaluate(watchExpressions, evaluationOptions);
+ this._currentFrameDescription = FRAME_TYPE.NORMAL;
+
+ // If an error was thrown during the evaluation of the watch expressions
+ // or the evaluation was terminated from the slow script dialog, then at
+ // least one expression evaluation could not be performed. So remove the
+ // most recent watch expression and try again.
+ if (this._currentEvaluation.throw || this._currentEvaluation.terminated) {
+ DebuggerView.WatchExpressions.removeAt(0);
+ yield DebuggerController.StackFrames.syncWatchExpressions();
+ }
+ }),
+
+ /**
+ * Adds the watch expressions evaluation results to a scope in the view.
+ *
+ * @param Scope aScope
+ * The scope where the watch expressions will be placed into.
+ * @param object aExp
+ * The grip of the evaluation results.
+ */
+ _fetchWatchExpressions: function(aScope, aExp) {
+ // Fetch the expressions only once.
+ if (aScope._fetched) {
+ return;
+ }
+ aScope._fetched = true;
+
+ // Add nodes for every watch expression in scope.
+ this.activeThread.pauseGrip(aExp).getPrototypeAndProperties(aResponse => {
+ let ownProperties = aResponse.ownProperties;
+ let totalExpressions = DebuggerView.WatchExpressions.itemCount;
+
+ for (let i = 0; i < totalExpressions; i++) {
+ let name = DebuggerView.WatchExpressions.getString(i);
+ let expVal = ownProperties[i].value;
+ let expRef = aScope.addItem(name, ownProperties[i]);
+ DebuggerView.Variables.controller.addExpander(expRef, expVal);
+
+ // Revert some of the custom watch expressions scope presentation flags,
+ // so that they don't propagate to child items.
+ expRef.switch = null;
+ expRef.delete = null;
+ expRef.descriptorTooltip = true;
+ expRef.separatorStr = L10N.getStr("variablesSeparatorLabel");
+ }
+
+ // Signal that watch expressions have been fetched.
+ window.emit(EVENTS.FETCHED_WATCH_EXPRESSIONS);
+ });
+ },
+
+ /**
+ * Updates a list of watch expressions to evaluate on each pause.
+ * TODO: handle all of this server-side: Bug 832470, comment 14.
+ */
+ syncWatchExpressions: function() {
+ let list = DebuggerView.WatchExpressions.getAllStrings();
+
+ // Sanity check all watch expressions before syncing them. To avoid
+ // having the whole watch expressions array throw because of a single
+ // faulty expression, simply convert it to a string describing the error.
+ // There's no other information necessary to be offered in such cases.
+ let sanitizedExpressions = list.map(aString => {
+ // Reflect.parse throws when it encounters a syntax error.
+ try {
+ Parser.reflectionAPI.parse(aString);
+ return aString; // Watch expression can be executed safely.
+ } catch (e) {
+ return "\"" + e.name + ": " + e.message + "\""; // Syntax error.
+ }
+ });
+
+ if (!sanitizedExpressions.length) {
+ this._currentWatchExpressions = null;
+ this._syncedWatchExpressions = null;
+ } else {
+ this._syncedWatchExpressions =
+ this._currentWatchExpressions = "[" +
+ sanitizedExpressions.map(aString =>
+ "eval(\"" +
+ "try {" +
+ // Make sure all quotes are escaped in the expression's syntax,
+ // and add a newline after the statement to avoid comments
+ // breaking the code integrity inside the eval block.
+ aString.replace(/"/g, "\\$&") + "\" + " + "'\\n'" + " + \"" +
+ "} catch (e) {" +
+ "e.name + ': ' + e.message;" + // TODO: Bug 812765, 812764.
+ "}" +
+ "\")"
+ ).join(",") +
+ "]";
+ }
+
+ this.currentFrameDepth = -1;
+ return this._onFrames();
+ }
+};
+
+/**
+ * Keeps the source script list up-to-date, using the thread client's
+ * source script cache.
+ */
+function SourceScripts() {
+ this._onNewGlobal = this._onNewGlobal.bind(this);
+ this._onNewSource = this._onNewSource.bind(this);
+ this._onSourcesAdded = this._onSourcesAdded.bind(this);
+ this._onBlackBoxChange = this._onBlackBoxChange.bind(this);
+ this._onPrettyPrintChange = this._onPrettyPrintChange.bind(this);
+}
+
+SourceScripts.prototype = {
+ get activeThread() DebuggerController.activeThread,
+ get debuggerClient() DebuggerController.client,
+ _cache: new Map(),
+
+ /**
+ * Connect to the current thread client.
+ */
+ connect: function() {
+ dumpn("SourceScripts is connecting...");
+ this.debuggerClient.addListener("newGlobal", this._onNewGlobal);
+ this.debuggerClient.addListener("newSource", this._onNewSource);
+ this.activeThread.addListener("blackboxchange", this._onBlackBoxChange);
+ this.activeThread.addListener("prettyprintchange", this._onPrettyPrintChange);
+ this.handleTabNavigation();
+ },
+
+ /**
+ * Disconnect from the client.
+ */
+ disconnect: function() {
+ if (!this.activeThread) {
+ return;
+ }
+ dumpn("SourceScripts is disconnecting...");
+ this.debuggerClient.removeListener("newGlobal", this._onNewGlobal);
+ this.debuggerClient.removeListener("newSource", this._onNewSource);
+ this.activeThread.removeListener("blackboxchange", this._onBlackBoxChange);
+ this.activeThread.addListener("prettyprintchange", this._onPrettyPrintChange);
+ },
+
+ /**
+ * Clears all the cached source contents.
+ */
+ clearCache: function() {
+ this._cache.clear();
+ },
+
+ /**
+ * Handles any initialization on a tab navigation event issued by the client.
+ */
+ handleTabNavigation: function() {
+ if (!this.activeThread) {
+ return;
+ }
+ dumpn("Handling tab navigation in the SourceScripts");
+
+ // Retrieve the list of script sources known to the server from before
+ // the client was ready to handle "newSource" notifications.
+ this.activeThread.getSources(this._onSourcesAdded);
+ },
+
+ /**
+ * Handler for the debugger client's unsolicited newGlobal notification.
+ */
+ _onNewGlobal: function(aNotification, aPacket) {
+ // TODO: bug 806775, update the globals list using aPacket.hostAnnotations
+ // from bug 801084.
+ },
+
+ /**
+ * Handler for the debugger client's unsolicited newSource notification.
+ */
+ _onNewSource: function(aNotification, aPacket) {
+ // Ignore bogus scripts, e.g. generated from 'clientEvaluate' packets.
+ if (NEW_SOURCE_IGNORED_URLS.indexOf(aPacket.source.url) != -1) {
+ return;
+ }
+
+ // Add the source in the debugger view sources container.
+ DebuggerView.Sources.addSource(aPacket.source, { staged: false });
+
+ // Select this source if it's the preferred one.
+ let preferredValue = DebuggerView.Sources.preferredValue;
+ if (aPacket.source.url == preferredValue) {
+ DebuggerView.Sources.selectedValue = preferredValue;
+ }
+ // ..or the first entry if there's none selected yet after a while
+ else {
+ setNamedTimeout("new-source", NEW_SOURCE_DISPLAY_DELAY, () => {
+ // If after a certain delay the preferred source still wasn't received,
+ // just give up on waiting and display the first entry.
+ if (!DebuggerView.Sources.selectedValue) {
+ DebuggerView.Sources.selectedIndex = 0;
+ }
+ });
+ }
+
+ // If there are any stored breakpoints for this source, display them again,
+ // both in the editor and the breakpoints pane.
+ DebuggerController.Breakpoints.updatePaneBreakpoints();
+ DebuggerController.Breakpoints.updateEditorBreakpoints();
+ DebuggerController.HitCounts.updateEditorHitCounts();
+
+ // Make sure the events listeners are up to date.
+ if (DebuggerView.instrumentsPaneTab == "events-tab") {
+ DebuggerController.Breakpoints.DOM.scheduleEventListenersFetch();
+ }
+
+ // Signal that a new source has been added.
+ window.emit(EVENTS.NEW_SOURCE);
+ },
+
+ /**
+ * Callback for the debugger's active thread getSources() method.
+ */
+ _onSourcesAdded: function(aResponse) {
+ if (aResponse.error) {
+ let msg = "Error getting sources: " + aResponse.message;
+ Cu.reportError(msg);
+ dumpn(msg);
+ return;
+ }
+
+ if (aResponse.sources.length === 0) {
+ DebuggerView.Sources.emptyText = L10N.getStr("noSourcesText");
+ window.emit(EVENTS.SOURCES_ADDED);
+ return;
+ }
+
+ // Add all the sources in the debugger view sources container.
+ for (let source of aResponse.sources) {
+ // Ignore bogus scripts, e.g. generated from 'clientEvaluate' packets.
+ if (NEW_SOURCE_IGNORED_URLS.indexOf(source.url) == -1) {
+ DebuggerView.Sources.addSource(source, { staged: true });
+ }
+ }
+
+ // Flushes all the prepared sources into the sources container.
+ DebuggerView.Sources.commit({ sorted: true });
+
+ // Select the preferred source if it exists and was part of the response.
+ let preferredValue = DebuggerView.Sources.preferredValue;
+ if (DebuggerView.Sources.containsValue(preferredValue)) {
+ DebuggerView.Sources.selectedValue = preferredValue;
+ }
+ // ..or the first entry if there's no one selected yet.
+ else if (!DebuggerView.Sources.selectedValue) {
+ DebuggerView.Sources.selectedIndex = 0;
+ }
+
+ // If there are any stored breakpoints for the sources, display them again,
+ // both in the editor and the breakpoints pane.
+ DebuggerController.Breakpoints.updatePaneBreakpoints();
+ DebuggerController.Breakpoints.updateEditorBreakpoints();
+ DebuggerController.HitCounts.updateEditorHitCounts();
+
+ // Signal that sources have been added.
+ window.emit(EVENTS.SOURCES_ADDED);
+ },
+
+ /**
+ * Handler for the debugger client's 'blackboxchange' notification.
+ */
+ _onBlackBoxChange: function (aEvent, { actor, isBlackBoxed }) {
+ const item = DebuggerView.Sources.getItemByValue(actor);
+ if (item) {
+ item.prebuiltNode.classList.toggle("black-boxed", isBlackBoxed);
+ }
+ DebuggerView.Sources.updateToolbarButtonsState();
+ DebuggerView.maybeShowBlackBoxMessage();
+ },
+
+ /**
+ * Set the black boxed status of the given source.
+ *
+ * @param Object aSource
+ * The source form.
+ * @param bool aBlackBoxFlag
+ * True to black box the source, false to un-black box it.
+ * @returns Promise
+ * A promize that resolves to [aSource, isBlackBoxed] or rejects to
+ * [aSource, error].
+ */
+ setBlackBoxing: function(aSource, aBlackBoxFlag) {
+ const sourceClient = this.activeThread.source(aSource);
+ const deferred = promise.defer();
+
+ sourceClient[aBlackBoxFlag ? "blackBox" : "unblackBox"](aPacket => {
+ const { error, message } = aPacket;
+ if (error) {
+ let msg = "Couldn't toggle black boxing for " + aSource.url + ": " + message;
+ dumpn(msg);
+ Cu.reportError(msg);
+ deferred.reject([aSource, msg]);
+ } else {
+ deferred.resolve([aSource, sourceClient.isBlackBoxed]);
+ }
+ });
+
+ return deferred.promise;
+ },
+
+ /**
+ * Toggle the pretty printing of a source's text. All subsequent calls to
+ * |getText| will return the pretty-toggled text. Nothing will happen for
+ * non-javascript files.
+ *
+ * @param Object aSource
+ * The source form from the RDP.
+ * @returns Promise
+ * A promise that resolves to [aSource, prettyText] or rejects to
+ * [aSource, error].
+ */
+ togglePrettyPrint: function(aSource) {
+ // Only attempt to pretty print JavaScript sources.
+ if (!SourceUtils.isJavaScript(aSource.url, aSource.contentType)) {
+ return promise.reject([aSource, "Can't prettify non-javascript files."]);
+ }
+
+ const sourceClient = this.activeThread.source(aSource);
+ const wantPretty = !sourceClient.isPrettyPrinted;
+
+ // Only use the existing promise if it is pretty printed.
+ let textPromise = this._cache.get(aSource.url);
+ if (textPromise && textPromise.pretty === wantPretty) {
+ return textPromise;
+ }
+
+ const deferred = promise.defer();
+ deferred.promise.pretty = wantPretty;
+ this._cache.set(aSource.actor, deferred.promise);
+
+ const afterToggle = ({ error, message, source: text, contentType }) => {
+ if (error) {
+ // Revert the rejected promise from the cache, so that the original
+ // source's text may be shown when the source is selected.
+ this._cache.set(aSource.actor, textPromise);
+ deferred.reject([aSource, message || error]);
+ return;
+ }
+ deferred.resolve([aSource, text, contentType]);
+ };
+
+ if (wantPretty) {
+ sourceClient.prettyPrint(Prefs.editorTabSize, afterToggle);
+ } else {
+ sourceClient.disablePrettyPrint(afterToggle);
+ }
+
+ return deferred.promise;
+ },
+
+ /**
+ * Handler for the debugger's prettyprintchange notification.
+ */
+ _onPrettyPrintChange: function(aEvent, { url }) {
+ // Remove the cached source AST from the Parser, to avoid getting
+ // wrong locations when searching for functions.
+ DebuggerController.Parser.clearSource(url);
+ },
+
+ /**
+ * Gets a specified source's text.
+ *
+ * @param object aSource
+ * The source object coming from the active thread.
+ * @param function aOnTimeout [optional]
+ * Function called when the source text takes a long time to fetch,
+ * but not necessarily failing. Long fetch times don't cause the
+ * rejection of the returned promise.
+ * @param number aDelay [optional]
+ * The amount of time it takes to consider a source slow to fetch.
+ * If unspecified, it defaults to a predefined value.
+ * @return object
+ * A promise that is resolved after the source text has been fetched.
+ */
+ getText: function(aSource, aOnTimeout, aDelay = FETCH_SOURCE_RESPONSE_DELAY) {
+ // Fetch the source text only once.
+ let textPromise = this._cache.get(aSource.actor);
+ if (textPromise) {
+ return textPromise;
+ }
+
+ let deferred = promise.defer();
+ this._cache.set(aSource.actor, deferred.promise);
+
+ // If the source text takes a long time to fetch, invoke a callback.
+ if (aOnTimeout) {
+ var fetchTimeout = window.setTimeout(() => aOnTimeout(aSource), aDelay);
+ }
+
+ // Get the source text from the active thread.
+ this.activeThread.source(aSource).source(({ error, source: text, contentType }) => {
+ if (aOnTimeout) {
+ window.clearTimeout(fetchTimeout);
+ }
+ if (error) {
+ deferred.reject([aSource, error]);
+ } else {
+ deferred.resolve([aSource, text, contentType]);
+ }
+ });
+
+ return deferred.promise;
+ },
+
+ /**
+ * Starts fetching all the sources, silently.
+ *
+ * @param array aUrls
+ * The urls for the sources to fetch. If fetching a source's text
+ * takes too long, it will be discarded.
+ * @return object
+ * A promise that is resolved after source texts have been fetched.
+ */
+ getTextForSources: function(aActors) {
+ let deferred = promise.defer();
+ let pending = new Set(aActors);
+ let fetched = [];
+
+ // Can't use promise.all, because if one fetch operation is rejected, then
+ // everything is considered rejected, thus no other subsequent source will
+ // be getting fetched. We don't want that. Something like Q's allSettled
+ // would work like a charm here.
+
+ // Try to fetch as many sources as possible.
+ for (let actor of aActors) {
+ let sourceItem = DebuggerView.Sources.getItemByValue(actor);
+ let sourceForm = sourceItem.attachment.source;
+ this.getText(sourceForm, onTimeout).then(onFetch, onError);
+ }
+
+ /* Called if fetching a source takes too long. */
+ function onTimeout(aSource) {
+ onError([aSource]);
+ }
+
+ /* Called if fetching a source finishes successfully. */
+ function onFetch([aSource, aText, aContentType]) {
+ // If fetching the source has previously timed out, discard it this time.
+ if (!pending.has(aSource.actor)) {
+ return;
+ }
+ pending.delete(aSource.actor);
+ fetched.push([aSource.actor, aText, aContentType]);
+ maybeFinish();
+ }
+
+ /* Called if fetching a source failed because of an error. */
+ function onError([aSource, aError]) {
+ pending.delete(aSource.actor);
+ maybeFinish();
+ }
+
+ /* Called every time something interesting happens while fetching sources. */
+ function maybeFinish() {
+ if (pending.size == 0) {
+ // Sort the fetched sources alphabetically by their url.
+ deferred.resolve(fetched.sort(([aFirst], [aSecond]) => aFirst > aSecond));
+ }
+ }
+
+ return deferred.promise;
+ }
+};
+
+/**
+ * Tracer update the UI according to the messages exchanged with the tracer
+ * actor.
+ */
+function Tracer() {
+ this._trace = null;
+ this._idCounter = 0;
+ this.onTraces = this.onTraces.bind(this);
+}
+
+Tracer.prototype = {
+ get client() {
+ return DebuggerController.client;
+ },
+
+ get traceClient() {
+ return DebuggerController.traceClient;
+ },
+
+ get tracing() {
+ return !!this._trace;
+ },
+
+ /**
+ * Hooks up the debugger controller with the tracer client.
+ */
+ connect: function() {
+ this._stack = [];
+ this.client.addListener("traces", this.onTraces);
+ },
+
+ /**
+ * Disconnects the debugger controller from the tracer client. Any further
+ * communcation with the tracer actor will not have any effect on the UI.
+ */
+ disconnect: function() {
+ this._stack = null;
+ this.client.removeListener("traces", this.onTraces);
+ },
+
+ /**
+ * Instructs the tracer actor to start tracing.
+ */
+ startTracing: function(aCallback = () => {}) {
+ if (this.tracing) {
+ return;
+ }
+
+ DebuggerView.Tracer.selectTab();
+
+ let id = this._trace = "dbg.trace" + Math.random();
+ let fields = [
+ "name",
+ "location",
+ "hitCount",
+ "parameterNames",
+ "depth",
+ "arguments",
+ "return",
+ "throw",
+ "yield"
+ ];
+
+ this.traceClient.startTrace(fields, id, aResponse => {
+ const { error } = aResponse;
+ if (error) {
+ DevToolsUtils.reportException("Tracer.prototype.startTracing", error);
+ this._trace = null;
+ }
+
+ aCallback(aResponse);
+ });
+ },
+
+ /**
+ * Instructs the tracer actor to stop tracing.
+ */
+ stopTracing: function(aCallback = () => {}) {
+ if (!this.tracing) {
+ return;
+ }
+ this.traceClient.stopTrace(this._trace, aResponse => {
+ const { error } = aResponse;
+ if (error) {
+ DevToolsUtils.reportException("Tracer.prototype.stopTracing", error);
+ }
+
+ this._trace = null;
+ DebuggerController.HitCounts.clear();
+ aCallback(aResponse);
+ });
+ },
+
+ onTraces: function (aEvent, { traces }) {
+ const tracesLength = traces.length;
+ let tracesToShow;
+
+ // Update hit counts.
+ for (let t of traces) {
+ if (t.type == "enteredFrame") {
+ DebuggerController.HitCounts.set(t.location, t.hitCount);
+ }
+ }
+ DebuggerController.HitCounts.updateEditorHitCounts();
+
+ // Limit number of traces to be shown in the log.
+ if (tracesLength > TracerView.MAX_TRACES) {
+ tracesToShow = traces.slice(tracesLength - TracerView.MAX_TRACES, tracesLength);
+ this._stack.splice(0, this._stack.length);
+ DebuggerView.Tracer.empty();
+ } else {
+ tracesToShow = traces;
+ }
+
+ // Show traces in the log.
+ for (let t of tracesToShow) {
+ if (t.type == "enteredFrame") {
+ this._onCall(t);
+ } else {
+ this._onReturn(t);
+ }
+ }
+ DebuggerView.Tracer.commit();
+ },
+
+ /**
+ * Callback for handling a new call frame.
+ */
+ _onCall: function({ name, location, blackBoxed, parameterNames, depth, arguments: args }) {
+ const item = {
+ name: name,
+ location: location,
+ id: this._idCounter++,
+ blackBoxed
+ };
+
+ this._stack.push(item);
+ DebuggerView.Tracer.addTrace({
+ type: "call",
+ name: name,
+ location: location,
+ depth: depth,
+ parameterNames: parameterNames,
+ arguments: args,
+ frameId: item.id,
+ blackBoxed
+ });
+ },
+
+ /**
+ * Callback for handling an exited frame.
+ */
+ _onReturn: function(aPacket) {
+ if (!this._stack.length) {
+ return;
+ }
+
+ const { name, id, location, blackBoxed } = this._stack.pop();
+ DebuggerView.Tracer.addTrace({
+ type: aPacket.why,
+ name: name,
+ location: location,
+ depth: aPacket.depth,
+ frameId: id,
+ returnVal: aPacket.return || aPacket.throw || aPacket.yield,
+ blackBoxed
+ });
+ },
+
+ /**
+ * Create an object which has the same interface as a normal object client,
+ * but since we already have all the information for an object that we will
+ * ever get (the server doesn't create actors when tracing, just firehoses
+ * data and forgets about it) just return the data immdiately.
+ *
+ * @param Object aObject
+ * The tracer object "grip" (more like a limited snapshot).
+ * @returns Object
+ * The synchronous client object.
+ */
+ syncGripClient: function(aObject) {
+ return {
+ get isFrozen() { return aObject.frozen; },
+ get isSealed() { return aObject.sealed; },
+ get isExtensible() { return aObject.extensible; },
+
+ get ownProperties() { return aObject.ownProperties; },
+ get prototype() { return null; },
+
+ getParameterNames: callback => callback(aObject),
+ getPrototypeAndProperties: callback => callback(aObject),
+ getPrototype: callback => callback(aObject),
+
+ getOwnPropertyNames: (callback) => {
+ callback({
+ ownPropertyNames: aObject.ownProperties
+ ? Object.keys(aObject.ownProperties)
+ : []
+ });
+ },
+
+ getProperty: (property, callback) => {
+ callback({
+ descriptor: aObject.ownProperties
+ ? aObject.ownProperties[property]
+ : null
+ });
+ },
+
+ getDisplayString: callback => callback("[object " + aObject.class + "]"),
+
+ getScope: callback => callback({
+ error: "scopeNotAvailable",
+ message: "Cannot get scopes for traced objects"
+ })
+ };
+ },
+
+ /**
+ * Wraps object snapshots received from the tracer server so that we can
+ * differentiate them from long living object grips from the debugger server
+ * in the variables view.
+ *
+ * @param Object aObject
+ * The object snapshot from the tracer actor.
+ */
+ WrappedObject: function(aObject) {
+ this.object = aObject;
+ }
+};
+
+/**
+ * Handles breaking on event listeners in the currently debugged target.
+ */
+function EventListeners() {
+}
+
+EventListeners.prototype = {
+ /**
+ * A list of event names on which the debuggee will automatically pause
+ * when invoked.
+ */
+ activeEventNames: [],
+
+ /**
+ * Updates the list of events types with listeners that, when invoked,
+ * will automatically pause the debuggee. The respective events are
+ * retrieved from the UI.
+ */
+ scheduleEventBreakpointsUpdate: function() {
+ // Make sure we're not sending a batch of closely repeated requests.
+ // This can easily happen when toggling all events of a certain type.
+ setNamedTimeout("event-breakpoints-update", 0, () => {
+ this.activeEventNames = DebuggerView.EventListeners.getCheckedEvents();
+ gThreadClient.pauseOnDOMEvents(this.activeEventNames);
+
+ // Notify that event breakpoints were added/removed on the server.
+ window.emit(EVENTS.EVENT_BREAKPOINTS_UPDATED);
+ });
+ },
+
+ /**
+ * Schedules fetching the currently attached event listeners from the debugee.
+ */
+ scheduleEventListenersFetch: function() {
+ // Make sure we're not sending a batch of closely repeated requests.
+ // This can easily happen whenever new sources are fetched.
+ setNamedTimeout("event-listeners-fetch", FETCH_EVENT_LISTENERS_DELAY, () => {
+ if (gThreadClient.state != "paused") {
+ gThreadClient.interrupt(() => this._getListeners(() => gThreadClient.resume()));
+ } else {
+ this._getListeners();
+ }
+ });
+ },
+
+ /**
+ * Fetches the currently attached event listeners from the debugee.
+ * The thread client state is assumed to be "paused".
+ *
+ * @param function aCallback
+ * Invoked once the event listeners are fetched and displayed.
+ */
+ _getListeners: function(aCallback) {
+ gThreadClient.eventListeners(Task.async(function*(aResponse) {
+ if (aResponse.error) {
+ throw "Error getting event listeners: " + aResponse.message;
+ }
+
+ // Make sure all the listeners are sorted by the event type, since
+ // they're not guaranteed to be clustered together.
+ aResponse.listeners.sort((a, b) => a.type > b.type ? 1 : -1);
+
+ // Add all the listeners in the debugger view event linsteners container.
+ for (let listener of aResponse.listeners) {
+ let definitionSite;
+ if (listener.function.class == "Function") {
+ definitionSite = yield this._getDefinitionSite(listener.function);
+ }
+ listener.function.url = definitionSite;
+ DebuggerView.EventListeners.addListener(listener, { staged: true });
+ }
+
+ // Flushes all the prepared events into the event listeners container.
+ DebuggerView.EventListeners.commit();
+
+ // Notify that event listeners were fetched and shown in the view,
+ // and callback to resume the active thread if necessary.
+ window.emit(EVENTS.EVENT_LISTENERS_FETCHED);
+ aCallback && aCallback();
+ }.bind(this)));
+ },
+
+ /**
+ * Gets a function's source-mapped definiton site.
+ *
+ * @param object aFunction
+ * The grip of the function to get the definition site for.
+ * @return object
+ * A promise that is resolved with the function's owner source url.
+ */
+ _getDefinitionSite: function(aFunction) {
+ let deferred = promise.defer();
+
+ gThreadClient.pauseGrip(aFunction).getDefinitionSite(aResponse => {
+ if (aResponse.error) {
+ // Don't make this error fatal, because it would break the entire events pane.
+ const msg = "Error getting function definition site: " + aResponse.message;
+ DevToolsUtils.reportException("_getDefinitionSite", msg);
+ }
+ deferred.resolve(aResponse.source.url);
+ });
+
+ return deferred.promise;
+ }
+};
+
+/**
+ * Handles all the breakpoints in the current debugger.
+ */
+function Breakpoints() {
+ this._onEditorBreakpointAdd = this._onEditorBreakpointAdd.bind(this);
+ this._onEditorBreakpointRemove = this._onEditorBreakpointRemove.bind(this);
+ this.addBreakpoint = this.addBreakpoint.bind(this);
+ this.removeBreakpoint = this.removeBreakpoint.bind(this);
+}
+
+Breakpoints.prototype = {
+ /**
+ * A map of breakpoint promises as tracked by the debugger frontend.
+ * The keys consist of a string representation of the breakpoint location.
+ */
+ _added: new Map(),
+ _removing: new Map(),
+ _disabled: new Map(),
+
+ /**
+ * Adds the source editor breakpoint handlers.
+ *
+ * @return object
+ * A promise that is resolved when the breakpoints finishes initializing.
+ */
+ initialize: function() {
+ DebuggerView.editor.on("breakpointAdded", this._onEditorBreakpointAdd);
+ DebuggerView.editor.on("breakpointRemoved", this._onEditorBreakpointRemove);
+
+ // Initialization is synchronous, for now.
+ return promise.resolve(null);
+ },
+
+ /**
+ * Removes the source editor breakpoint handlers & all the added breakpoints.
+ *
+ * @return object
+ * A promise that is resolved when the breakpoints finishes destroying.
+ */
+ destroy: function() {
+ DebuggerView.editor.off("breakpointAdded", this._onEditorBreakpointAdd);
+ DebuggerView.editor.off("breakpointRemoved", this._onEditorBreakpointRemove);
+
+ return this.removeAllBreakpoints();
+ },
+
+ /**
+ * Event handler for new breakpoints that come from the editor.
+ *
+ * @param number aLine
+ * Line number where breakpoint was set.
+ */
+ _onEditorBreakpointAdd: Task.async(function*(_, aLine) {
+ let actor = DebuggerView.Sources.selectedValue;
+ let location = { actor: actor, line: aLine + 1 };
+
+ // Initialize the breakpoint, but don't update the editor, since this
+ // callback is invoked because a breakpoint was added in the
+ // editor itself.
+ let breakpointClient = yield this.addBreakpoint(location, { noEditorUpdate: true });
+
+ // If the breakpoint client has a "requestedLocation" attached, then
+ // the original requested placement for the breakpoint wasn't accepted.
+ // In this case, we need to update the editor with the new location.
+ if (breakpointClient.requestedLocation) {
+ DebuggerView.editor.moveBreakpoint(
+ breakpointClient.requestedLocation.line - 1,
+ breakpointClient.location.line - 1
+ );
+ }
+
+ // Notify that we've shown a breakpoint in the source editor.
+ window.emit(EVENTS.BREAKPOINT_SHOWN_IN_EDITOR);
+ }),
+
+ /**
+ * Event handler for breakpoints that are removed from the editor.
+ *
+ * @param number aLine
+ * Line number where breakpoint was removed.
+ */
+ _onEditorBreakpointRemove: Task.async(function*(_, aLine) {
+ let actor = DebuggerView.Sources.selectedValue;
+ let location = { actor: actor, line: aLine + 1 };
+ yield this.removeBreakpoint(location, { noEditorUpdate: true });
+
+ // Notify that we've hidden a breakpoint in the source editor.
+ window.emit(EVENTS.BREAKPOINT_HIDDEN_IN_EDITOR);
+ }),
+
+ /**
+ * Update the breakpoints in the editor view. This function takes the list of
+ * breakpoints in the debugger and adds them back into the editor view.
+ * This is invoked when the selected script is changed, or when new sources
+ * are received via the _onNewSource and _onSourcesAdded event listeners.
+ */
+ updateEditorBreakpoints: Task.async(function*() {
+ for (let breakpointPromise of this._addedOrDisabled) {
+ let breakpointClient = yield breakpointPromise;
+ let location = breakpointClient.location;
+ let currentSourceActor = DebuggerView.Sources.selectedValue;
+ let sourceActor = DebuggerView.Sources.getActorForLocation(location);
+
+ // Update the view only if the breakpoint is in the currently
+ // shown source.
+ if (currentSourceActor === sourceActor) {
+ yield this._showBreakpoint(breakpointClient, { noPaneUpdate: true });
+ }
+ }
+ }),
+
+ /**
+ * Update the breakpoints in the pane view. This function takes the list of
+ * breakpoints in the debugger and adds them back into the breakpoints pane.
+ * This is invoked when new sources are received via the _onNewSource and
+ * _onSourcesAdded event listeners.
+ */
+ updatePaneBreakpoints: Task.async(function*() {
+ for (let breakpointPromise of this._addedOrDisabled) {
+ let breakpointClient = yield breakpointPromise;
+ let container = DebuggerView.Sources;
+ let sourceActor = breakpointClient.location.actor;
+
+ // Update the view only if the breakpoint exists in a known source.
+ if (container.containsValue(sourceActor)) {
+ yield this._showBreakpoint(breakpointClient, { noEditorUpdate: true });
+ }
+ }
+ }),
+
+ /**
+ * Add a breakpoint.
+ *
+ * @param object aLocation
+ * The location where you want the breakpoint.
+ * This object must have two properties:
+ * - url: the breakpoint's source location.
+ * - line: the breakpoint's line number.
+ * It can also have the following optional properties:
+ * - condition: only pause if this condition evaluates truthy
+ * @param object aOptions [optional]
+ * Additional options or flags supported by this operation:
+ * - openPopup: tells if the expression popup should be shown.
+ * - noEditorUpdate: tells if you want to skip editor updates.
+ * - noPaneUpdate: tells if you want to skip breakpoint pane updates.
+ * @return object
+ * A promise that is resolved after the breakpoint is added, or
+ * rejected if there was an error.
+ */
+ addBreakpoint: Task.async(function*(aLocation, aOptions = {}) {
+ // Make sure a proper location is available.
+ if (!aLocation) {
+ throw new Error("Invalid breakpoint location.");
+ }
+ let addedPromise, removingPromise;
+
+ // If the breakpoint was already added, or is currently being added at the
+ // specified location, then return that promise immediately.
+ if ((addedPromise = this._getAdded(aLocation))) {
+ return addedPromise;
+ }
+
+ // If the breakpoint is currently being removed from the specified location,
+ // then wait for that to finish.
+ if ((removingPromise = this._getRemoving(aLocation))) {
+ yield removingPromise;
+ }
+
+ let deferred = promise.defer();
+
+ // Remember the breakpoint initialization promise in the store.
+ let identifier = this.getIdentifier(aLocation);
+ this._added.set(identifier, deferred.promise);
+
+ let source = gThreadClient.source(
+ DebuggerView.Sources.getItemByValue(aLocation.actor).attachment.source
+ );
+
+ source.setBreakpoint(aLocation, Task.async(function*(aResponse, aBreakpointClient) {
+ // If the breakpoint response has an "actualLocation" attached, then
+ // the original requested placement for the breakpoint wasn't accepted.
+ if (aResponse.actualLocation) {
+ // Remember the initialization promise for the new location instead.
+ let oldIdentifier = identifier;
+ let newIdentifier = identifier = this.getIdentifier(aResponse.actualLocation);
+ this._added.delete(oldIdentifier);
+ this._added.set(newIdentifier, deferred.promise);
+ }
+
+ // By default, new breakpoints are always enabled. Disabled breakpoints
+ // are, in fact, removed from the server but preserved in the frontend,
+ // so that they may not be forgotten across target navigations.
+ let disabledPromise = this._disabled.get(identifier);
+ if (disabledPromise) {
+ let aPrevBreakpointClient = yield disabledPromise;
+ let condition = aPrevBreakpointClient.getCondition();
+ this._disabled.delete(identifier);
+
+ if (condition) {
+ aBreakpointClient = yield aBreakpointClient.setCondition(
+ gThreadClient,
+ condition
+ );
+ }
+ }
+
+ if (aResponse.actualLocation) {
+ // Store the originally requested location in case it's ever needed
+ // and update the breakpoint client with the actual location.
+ let actualLoc = aResponse.actualLocation;
+ aBreakpointClient.requestedLocation = aLocation;
+ aBreakpointClient.location = actualLoc;
+ aBreakpointClient.location.actor = actualLoc.source ? actualLoc.source.actor : null;
+ }
+
+ // Preserve information about the breakpoint's line text, to display it
+ // in the sources pane without requiring fetching the source (for example,
+ // after the target navigated). Note that this will get out of sync
+ // if the source text contents change.
+ let line = aBreakpointClient.location.line - 1;
+ aBreakpointClient.text = DebuggerView.editor.getText(line).trim();
+
+ // Show the breakpoint in the editor and breakpoints pane, and
+ // resolve.
+ yield this._showBreakpoint(aBreakpointClient, aOptions);
+
+ // Notify that we've added a breakpoint.
+ window.emit(EVENTS.BREAKPOINT_ADDED, aBreakpointClient);
+ deferred.resolve(aBreakpointClient);
+ }.bind(this)));
+
+ return deferred.promise;
+ }),
+
+ /**
+ * Remove a breakpoint.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ * @param object aOptions [optional]
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ * @return object
+ * A promise that is resolved after the breakpoint is removed, or
+ * rejected if there was an error.
+ */
+ removeBreakpoint: function(aLocation, aOptions = {}) {
+ // Make sure a proper location is available.
+ if (!aLocation) {
+ return promise.reject(new Error("Invalid breakpoint location."));
+ }
+
+ // If the breakpoint was already removed, or has never even been added,
+ // then return a resolved promise immediately.
+ let addedPromise = this._getAdded(aLocation);
+ if (!addedPromise) {
+ return promise.resolve(aLocation);
+ }
+
+ // If the breakpoint is currently being removed from the specified location,
+ // then return that promise immediately.
+ let removingPromise = this._getRemoving(aLocation);
+ if (removingPromise) {
+ return removingPromise;
+ }
+
+ let deferred = promise.defer();
+
+ // Remember the breakpoint removal promise in the store.
+ let identifier = this.getIdentifier(aLocation);
+ this._removing.set(identifier, deferred.promise);
+
+ // Retrieve the corresponding breakpoint client first.
+ addedPromise.then(aBreakpointClient => {
+ // Try removing the breakpoint.
+ aBreakpointClient.remove(aResponse => {
+ // If there was an error removing the breakpoint, reject the promise
+ // and forget about it that the breakpoint may be re-removed later.
+ if (aResponse.error) {
+ deferred.reject(aResponse);
+ return void this._removing.delete(identifier);
+ }
+
+ // When a breakpoint is removed, the frontend may wish to preserve some
+ // details about it, so that it can be easily re-added later. In such
+ // cases, breakpoints are marked and stored as disabled, so that they
+ // may not be forgotten across target navigations.
+ if (aOptions.rememberDisabled) {
+ aBreakpointClient.disabled = true;
+ this._disabled.set(identifier, promise.resolve(aBreakpointClient));
+ }
+
+ // Forget both the initialization and removal promises from the store.
+ this._added.delete(identifier);
+ this._removing.delete(identifier);
+
+ // Hide the breakpoint from the editor and breakpoints pane, and resolve.
+ this._hideBreakpoint(aLocation, aOptions);
+
+ // Notify that we've removed a breakpoint.
+ window.emit(EVENTS.BREAKPOINT_REMOVED, aLocation);
+ deferred.resolve(aLocation);
+ });
+ });
+
+ return deferred.promise;
+ },
+
+ /**
+ * Removes all the currently enabled breakpoints.
+ *
+ * @return object
+ * A promise that is resolved after all breakpoints are removed, or
+ * rejected if there was an error.
+ */
+ removeAllBreakpoints: function() {
+ /* Gets an array of all the existing breakpoints promises. */
+ let getActiveBreakpoints = (aPromises, aStore = []) => {
+ for (let [, breakpointPromise] of aPromises) {
+ aStore.push(breakpointPromise);
+ }
+ return aStore;
+ }
+
+ /* Gets an array of all the removed breakpoints promises. */
+ let getRemovedBreakpoints = (aClients, aStore = []) => {
+ for (let breakpointClient of aClients) {
+ aStore.push(this.removeBreakpoint(breakpointClient.location));
+ }
+ return aStore;
+ }
+
+ // First, populate an array of all the currently added breakpoints promises.
+ // Then, once all the breakpoints clients are retrieved, populate an array
+ // of all the removed breakpoints promises and wait for their fulfillment.
+ return promise.all(getActiveBreakpoints(this._added)).then(aBreakpointClients => {
+ return promise.all(getRemovedBreakpoints(aBreakpointClients));
+ });
+ },
+
+ /**
+ * Update the condition of a breakpoint.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ * @param string aClients
+ * The condition to set on the breakpoint
+ * @return object
+ * A promise that will be resolved with the breakpoint client
+ */
+ updateCondition: Task.async(function*(aLocation, aCondition) {
+ let addedPromise = this._getAdded(aLocation);
+ if (!addedPromise) {
+ throw new Error("Breakpoint does not exist at the specified location");
+ }
+ let breakpointClient = yield addedPromise;
+ let promise = breakpointClient.setCondition(gThreadClient, aCondition);
+
+ // `setCondition` returns a new breakpoint that has the condition,
+ // so we need to update the store
+ this._added.set(this.getIdentifier(aLocation), promise);
+ return promise;
+ }),
+
+ /**
+ * Update the editor and breakpoints pane to show a specified breakpoint.
+ *
+ * @param object aBreakpointClient
+ * A BreakpointClient instance.
+ * This object has additional properties dynamically added by
+ * our code:
+ * - disabled: the breakpoint's disabled state, boolean
+ * - text: the breakpoint's line text to be displayed
+ * @param object aOptions [optional]
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _showBreakpoint: function(aBreakpointClient, aOptions = {}) {
+ let tasks = [];
+ let currentSourceActor = DebuggerView.Sources.selectedValue;
+ let location = aBreakpointClient.location;
+ let actor = DebuggerView.Sources.getActorForLocation(location);
+
+ // Update the editor if required.
+ if (!aOptions.noEditorUpdate && !aBreakpointClient.disabled) {
+ if (currentSourceActor === actor) {
+ tasks.push(DebuggerView.editor.addBreakpoint(location.line - 1));
+ }
+ }
+
+ // Update the breakpoints pane if required.
+ if (!aOptions.noPaneUpdate) {
+ DebuggerView.Sources.addBreakpoint(aBreakpointClient, aOptions);
+ }
+
+ return promise.all(tasks);
+ },
+
+ /**
+ * Update the editor and breakpoints pane to hide a specified breakpoint.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ * @param object aOptions [optional]
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _hideBreakpoint: function(aLocation, aOptions = {}) {
+ let currentSourceActor = DebuggerView.Sources.selectedValue;
+ let actor = DebuggerView.Sources.getActorForLocation(aLocation);
+
+ // Update the editor if required.
+ if (!aOptions.noEditorUpdate) {
+ if (currentSourceActor === actor) {
+ DebuggerView.editor.removeBreakpoint(aLocation.line - 1);
+ }
+ }
+
+ // Update the breakpoints pane if required.
+ if (!aOptions.noPaneUpdate) {
+ DebuggerView.Sources.removeBreakpoint(aLocation);
+ }
+ },
+
+ /**
+ * Get a Promise for the BreakpointActor client object which is already added
+ * or currently being added at the given location.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ * @return object | null
+ * A promise that is resolved after the breakpoint is added, or
+ * null if no breakpoint was found.
+ */
+ _getAdded: function(aLocation) {
+ return this._added.get(this.getIdentifier(aLocation));
+ },
+
+ /**
+ * Get a Promise for the BreakpointActor client object which is currently
+ * being removed from the given location.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ * @return object | null
+ * A promise that is resolved after the breakpoint is removed, or
+ * null if no breakpoint was found.
+ */
+ _getRemoving: function(aLocation) {
+ return this._removing.get(this.getIdentifier(aLocation));
+ },
+
+ /**
+ * Get an identifier string for a given location. Breakpoint promises are
+ * identified in the store by a string representation of their location.
+ *
+ * @param object aLocation
+ * The location to serialize to a string.
+ * @return string
+ * The identifier string.
+ */
+ getIdentifier: function(aLocation) {
+ return (aLocation.source ? aLocation.source.actor : aLocation.actor) +
+ ":" + aLocation.line;
+ }
+};
+
+/**
+ * Gets all Promises for the BreakpointActor client objects that are
+ * either enabled (added to the server) or disabled (removed from the server,
+ * but for which some details are preserved).
+ */
+Object.defineProperty(Breakpoints.prototype, "_addedOrDisabled", {
+ get: function* () {
+ yield* this._added.values();
+ yield* this._disabled.values();
+ }
+});
+
+/**
+ * Handles Tracer's hit counts.
+ */
+function HitCounts() {
+ /**
+ * Storage of hit counts for every location
+ * hitCount = _locations[url][line][column]
+ */
+ this._hitCounts = Object.create(null);
+}
+
+HitCounts.prototype = {
+ set: function({url, line, column}, aHitCount) {
+ if (url) {
+ if (!this._hitCounts[url]) {
+ this._hitCounts[url] = Object.create(null);
+ }
+ if (!this._hitCounts[url][line]) {
+ this._hitCounts[url][line] = Object.create(null);
+ }
+ this._hitCounts[url][line][column] = aHitCount;
+ }
+ },
+
+ /**
+ * Update all the hit counts in the editor view. This is invoked when the
+ * selected script is changed, or when new sources are received via the
+ * _onNewSource and _onSourcesAdded event listeners.
+ */
+ updateEditorHitCounts: function() {
+ // First, remove all hit counters.
+ DebuggerView.editor.removeAllMarkers("hit-counts");
+
+ // Then, add new hit counts, just for the current source.
+ for (let url in this._hitCounts) {
+ for (let line in this._hitCounts[url]) {
+ for (let column in this._hitCounts[url][line]) {
+ this._updateEditorHitCount({url, line, column});
+ }
+ }
+ }
+ },
+
+ /**
+ * Update a hit counter on a certain line.
+ */
+ _updateEditorHitCount: function({url, line, column}) {
+ // Editor must be initialized.
+ if (!DebuggerView.editor) {
+ return;
+ }
+
+ // No need to do anything if the counter's source is not being shown in the
+ // editor.
+ if (url &&
+ DebuggerView.Sources.selectedItem.attachment.source.url != url) {
+ return;
+ }
+
+ // There might be more counters on the same line. We need to combine them
+ // into one.
+ let content = Object.keys(this._hitCounts[url][line])
+ .sort() // Sort by key (column).
+ .map(a => this._hitCounts[url][line][a]) // Extract values.
+ .map(a => a + "\u00D7") // Format hit count (e.g. 146×).
+ .join("|");
+
+ // CodeMirror's lines are indexed from 0, while traces start from 1
+ DebuggerView.editor.addContentMarker(line - 1, "hit-counts", "hit-count",
+ content);
+ },
+
+ /**
+ * Remove all hit couters and clear the storage
+ */
+ clear: function() {
+ DebuggerView.editor.removeAllMarkers("hit-counts");
+ this._hitCounts = Object.create(null);
+ }
+}
+
+/**
+ * Localization convenience methods.
+ */
+let L10N = new ViewHelpers.L10N(DBG_STRINGS_URI);
+
+/**
+ * Shortcuts for accessing various debugger preferences.
+ */
+let Prefs = new ViewHelpers.Prefs("devtools", {
+ sourcesWidth: ["Int", "debugger.ui.panes-sources-width"],
+ instrumentsWidth: ["Int", "debugger.ui.panes-instruments-width"],
+ panesVisibleOnStartup: ["Bool", "debugger.ui.panes-visible-on-startup"],
+ variablesSortingEnabled: ["Bool", "debugger.ui.variables-sorting-enabled"],
+ variablesOnlyEnumVisible: ["Bool", "debugger.ui.variables-only-enum-visible"],
+ variablesSearchboxVisible: ["Bool", "debugger.ui.variables-searchbox-visible"],
+ pauseOnExceptions: ["Bool", "debugger.pause-on-exceptions"],
+ ignoreCaughtExceptions: ["Bool", "debugger.ignore-caught-exceptions"],
+ sourceMapsEnabled: ["Bool", "debugger.source-maps-enabled"],
+ prettyPrintEnabled: ["Bool", "debugger.pretty-print-enabled"],
+ autoPrettyPrint: ["Bool", "debugger.auto-pretty-print"],
+ tracerEnabled: ["Bool", "debugger.tracer"],
+ editorTabSize: ["Int", "editor.tabsize"],
+ autoBlackBox: ["Bool", "debugger.auto-black-box"]
+});
+
+/**
+ * Convenient way of emitting events from the panel window.
+ */
+EventEmitter.decorate(this);
+
+/**
+ * Preliminary setup for the DebuggerController object.
+ */
+DebuggerController.initialize();
+DebuggerController.Parser = new Parser();
+DebuggerController.ThreadState = new ThreadState();
+DebuggerController.StackFrames = new StackFrames();
+DebuggerController.SourceScripts = new SourceScripts();
+DebuggerController.Breakpoints = new Breakpoints();
+DebuggerController.Breakpoints.DOM = new EventListeners();
+DebuggerController.Tracer = new Tracer();
+DebuggerController.HitCounts = new HitCounts();
+
+/**
+ * Export some properties to the global scope for easier access.
+ */
+Object.defineProperties(window, {
+ "gTarget": {
+ get: function() DebuggerController._target,
+ configurable: true
+ },
+ "gHostType": {
+ get: function() DebuggerView._hostType,
+ configurable: true
+ },
+ "gClient": {
+ get: function() DebuggerController.client,
+ configurable: true
+ },
+ "gThreadClient": {
+ get: function() DebuggerController.activeThread,
+ configurable: true
+ },
+ "gCallStackPageSize": {
+ get: function() CALL_STACK_PAGE_SIZE,
+ configurable: true
+ }
+});
+
+/**
+ * Helper method for debugging.
+ * @param string
+ */
+function dumpn(str) {
+ if (wantLogging) {
+ dump("DBG-FRONTEND: " + str + "\n");
+ }
+}
+
+let wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
diff --git a/toolkit/devtools/debugger/debugger-panes.js b/toolkit/devtools/debugger/debugger-panes.js
new file mode 100644
index 000000000..b01d4a32e
--- /dev/null
+++ b/toolkit/devtools/debugger/debugger-panes.js
@@ -0,0 +1,3420 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// Used to detect minification for automatic pretty printing
+const SAMPLE_SIZE = 50; // no of lines
+const INDENT_COUNT_THRESHOLD = 5; // percentage
+const CHARACTER_LIMIT = 250; // line character limit
+
+// Maps known URLs to friendly source group names and put them at the
+// bottom of source list.
+const KNOWN_SOURCE_GROUPS = {
+ "Add-on SDK": "resource://gre/modules/commonjs/",
+};
+
+KNOWN_SOURCE_GROUPS[L10N.getStr("evalGroupLabel")] = "eval";
+
+/**
+ * Functions handling the sources UI.
+ */
+function SourcesView() {
+ dumpn("SourcesView was instantiated");
+
+ this.togglePrettyPrint = this.togglePrettyPrint.bind(this);
+ this.toggleBlackBoxing = this.toggleBlackBoxing.bind(this);
+ this.toggleBreakpoints = this.toggleBreakpoints.bind(this);
+
+ this._onEditorLoad = this._onEditorLoad.bind(this);
+ this._onEditorUnload = this._onEditorUnload.bind(this);
+ this._onEditorCursorActivity = this._onEditorCursorActivity.bind(this);
+ this._onSourceSelect = this._onSourceSelect.bind(this);
+ this._onStopBlackBoxing = this._onStopBlackBoxing.bind(this);
+ this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
+ this._onBreakpointClick = this._onBreakpointClick.bind(this);
+ this._onBreakpointCheckboxClick = this._onBreakpointCheckboxClick.bind(this);
+ this._onConditionalPopupShowing = this._onConditionalPopupShowing.bind(this);
+ this._onConditionalPopupShown = this._onConditionalPopupShown.bind(this);
+ this._onConditionalPopupHiding = this._onConditionalPopupHiding.bind(this);
+ this._onConditionalTextboxKeyPress = this._onConditionalTextboxKeyPress.bind(this);
+}
+
+SourcesView.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function() {
+ dumpn("Initializing the SourcesView");
+
+ this.widget = new SideMenuWidget(document.getElementById("sources"), {
+ showArrows: true
+ });
+
+ this.emptyText = L10N.getStr("noSourcesText");
+ this._blackBoxCheckboxTooltip = L10N.getStr("blackBoxCheckboxTooltip");
+
+ this._commandset = document.getElementById("debuggerCommands");
+ this._popupset = document.getElementById("debuggerPopupset");
+ this._cmPopup = document.getElementById("sourceEditorContextMenu");
+ this._cbPanel = document.getElementById("conditional-breakpoint-panel");
+ this._cbTextbox = document.getElementById("conditional-breakpoint-panel-textbox");
+ this._blackBoxButton = document.getElementById("black-box");
+ this._stopBlackBoxButton = document.getElementById("black-boxed-message-button");
+ this._prettyPrintButton = document.getElementById("pretty-print");
+ this._toggleBreakpointsButton = document.getElementById("toggle-breakpoints");
+
+ if (Prefs.prettyPrintEnabled) {
+ this._prettyPrintButton.removeAttribute("hidden");
+ }
+
+ window.on(EVENTS.EDITOR_LOADED, this._onEditorLoad, false);
+ window.on(EVENTS.EDITOR_UNLOADED, this._onEditorUnload, false);
+ this.widget.addEventListener("select", this._onSourceSelect, false);
+ this._stopBlackBoxButton.addEventListener("click", this._onStopBlackBoxing, false);
+ this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false);
+ this._cbPanel.addEventListener("popupshown", this._onConditionalPopupShown, false);
+ this._cbPanel.addEventListener("popuphiding", this._onConditionalPopupHiding, false);
+ this._cbTextbox.addEventListener("keypress", this._onConditionalTextboxKeyPress, false);
+
+ this.autoFocusOnSelection = false;
+
+ // Sort the contents by the displayed label.
+ this.sortContents((aFirst, aSecond) => {
+ return +(aFirst.attachment.label.toLowerCase() >
+ aSecond.attachment.label.toLowerCase());
+ });
+
+ // Sort known source groups towards the end of the list
+ this.widget.groupSortPredicate = function(a, b) {
+ if ((a in KNOWN_SOURCE_GROUPS) == (b in KNOWN_SOURCE_GROUPS)) {
+ return a.localeCompare(b);
+ }
+ return (a in KNOWN_SOURCE_GROUPS) ? 1 : -1;
+ };
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function() {
+ dumpn("Destroying the SourcesView");
+
+ window.off(EVENTS.EDITOR_LOADED, this._onEditorLoad, false);
+ window.off(EVENTS.EDITOR_UNLOADED, this._onEditorUnload, false);
+ this.widget.removeEventListener("select", this._onSourceSelect, false);
+ this._stopBlackBoxButton.removeEventListener("click", this._onStopBlackBoxing, false);
+ this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
+ this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShown, false);
+ this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false);
+ this._cbTextbox.removeEventListener("keypress", this._onConditionalTextboxKeyPress, false);
+ },
+
+ /**
+ * Sets the preferred location to be selected in this sources container.
+ * @param string aUrl
+ */
+ set preferredSource(aUrl) {
+ this._preferredValue = aUrl;
+
+ // Selects the element with the specified value in this sources container,
+ // if already inserted.
+ if (this.containsValue(aUrl)) {
+ this.selectedValue = aUrl;
+ }
+ },
+
+ /**
+ * Adds a source to this sources container.
+ *
+ * @param object aSource
+ * The source object coming from the active thread.
+ * @param object aOptions [optional]
+ * Additional options for adding the source. Supported options:
+ * - staged: true to stage the item to be appended later
+ */
+ addSource: function(aSource, aOptions = {}) {
+ if (!aSource.url) {
+ // We don't show any unnamed eval scripts yet (see bug 1124106)
+ return;
+ }
+
+ let { label, group, unicodeUrl } = this._parseUrl(aSource);
+
+ let contents = document.createElement("label");
+ contents.className = "plain dbg-source-item";
+ contents.setAttribute("value", label);
+ contents.setAttribute("crop", "start");
+ contents.setAttribute("flex", "1");
+ contents.setAttribute("tooltiptext", unicodeUrl);
+
+ // If the source is blackboxed, apply the appropriate style.
+ if (gThreadClient.source(aSource).isBlackBoxed) {
+ contents.classList.add("black-boxed");
+ }
+
+ // Append a source item to this container.
+ this.push([contents, aSource.actor], {
+ staged: aOptions.staged, /* stage the item to be appended later? */
+ attachment: {
+ label: label,
+ group: group,
+ checkboxState: !aSource.isBlackBoxed,
+ checkboxTooltip: this._blackBoxCheckboxTooltip,
+ source: aSource
+ }
+ });
+ },
+
+ _parseUrl: function(aSource) {
+ let fullUrl = aSource.url;
+ let url = fullUrl.split(" -> ").pop();
+ let label = aSource.addonPath ? aSource.addonPath : SourceUtils.getSourceLabel(url);
+ let group = aSource.addonID ? aSource.addonID : SourceUtils.getSourceGroup(url);
+
+ return {
+ label: label,
+ group: group,
+ unicodeUrl: NetworkHelper.convertToUnicode(unescape(fullUrl))
+ };
+ },
+
+ /**
+ * Adds a breakpoint to this sources container.
+ *
+ * @param object aBreakpointClient
+ * See Breakpoints.prototype._showBreakpoint
+ * @param object aOptions [optional]
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ addBreakpoint: function(aBreakpointClient, aOptions = {}) {
+ let { location, disabled } = aBreakpointClient;
+
+ // Make sure we're not duplicating anything. If a breakpoint at the
+ // specified source url and line already exists, just toggle it.
+ if (this.getBreakpoint(location)) {
+ this[disabled ? "disableBreakpoint" : "enableBreakpoint"](location);
+ return;
+ }
+
+ // Get the source item to which the breakpoint should be attached.
+ let sourceItem = this.getItemByValue(this.getActorForLocation(location));
+
+ // Create the element node and menu popup for the breakpoint item.
+ let breakpointArgs = Heritage.extend(aBreakpointClient, aOptions);
+ let breakpointView = this._createBreakpointView.call(this, breakpointArgs);
+ let contextMenu = this._createContextMenu.call(this, breakpointArgs);
+
+ // Append a breakpoint child item to the corresponding source item.
+ sourceItem.append(breakpointView.container, {
+ attachment: Heritage.extend(breakpointArgs, {
+ actor: location.actor,
+ line: location.line,
+ view: breakpointView,
+ popup: contextMenu
+ }),
+ attributes: [
+ ["contextmenu", contextMenu.menupopupId]
+ ],
+ // Make sure that when the breakpoint item is removed, the corresponding
+ // menupopup and commandset are also destroyed.
+ finalize: this._onBreakpointRemoved
+ });
+
+ // Highlight the newly appended breakpoint child item if
+ // necessary.
+ if (aOptions.openPopup || !aOptions.noEditorUpdate) {
+ this.highlightBreakpoint(location, aOptions);
+ }
+
+ window.emit(EVENTS.BREAKPOINT_SHOWN_IN_PANE);
+ },
+
+ /**
+ * Removes a breakpoint from this sources container.
+ * It does not also remove the breakpoint from the controller. Be careful.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ removeBreakpoint: function(aLocation) {
+ // When a parent source item is removed, all the child breakpoint items are
+ // also automagically removed.
+ let sourceItem = this.getItemByValue(aLocation.actor);
+ if (!sourceItem) {
+ return;
+ }
+ let breakpointItem = this.getBreakpoint(aLocation);
+ if (!breakpointItem) {
+ return;
+ }
+
+ // Clear the breakpoint view.
+ sourceItem.remove(breakpointItem);
+
+ window.emit(EVENTS.BREAKPOINT_HIDDEN_IN_PANE);
+ },
+
+ /**
+ * Returns the breakpoint at the specified source url and line.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ * @return object
+ * The corresponding breakpoint item if found, null otherwise.
+ */
+ getBreakpoint: function(aLocation) {
+ return this.getItemForPredicate(aItem =>
+ aItem.attachment.actor == aLocation.actor &&
+ aItem.attachment.line == aLocation.line);
+ },
+
+ /**
+ * Returns all breakpoints for all sources.
+ *
+ * @return array
+ * The breakpoints for all sources if any, an empty array otherwise.
+ */
+ getAllBreakpoints: function(aStore = []) {
+ return this.getOtherBreakpoints(undefined, aStore);
+ },
+
+ /**
+ * Returns all breakpoints which are not at the specified source url and line.
+ *
+ * @param object aLocation [optional]
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ * @param array aStore [optional]
+ * A list in which to store the corresponding breakpoints.
+ * @return array
+ * The corresponding breakpoints if found, an empty array otherwise.
+ */
+ getOtherBreakpoints: function(aLocation = {}, aStore = []) {
+ for (let source of this) {
+ for (let breakpointItem of source) {
+ let { actor, line } = breakpointItem.attachment;
+ if (actor != aLocation.actor || line != aLocation.line) {
+ aStore.push(breakpointItem);
+ }
+ }
+ }
+ return aStore;
+ },
+
+ /**
+ * Enables a breakpoint.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ * @param object aOptions [optional]
+ * Additional options or flags supported by this operation:
+ * - silent: pass true to not update the checkbox checked state;
+ * this is usually necessary when the checked state will
+ * be updated automatically (e.g: on a checkbox click).
+ * @return object
+ * A promise that is resolved after the breakpoint is enabled, or
+ * rejected if no breakpoint was found at the specified location.
+ */
+ enableBreakpoint: function(aLocation, aOptions = {}) {
+ let breakpointItem = this.getBreakpoint(aLocation);
+ if (!breakpointItem) {
+ return promise.reject(new Error("No breakpoint found."));
+ }
+
+ // Breakpoint will now be enabled.
+ let attachment = breakpointItem.attachment;
+ attachment.disabled = false;
+
+ // Update the corresponding menu items to reflect the enabled state.
+ let prefix = "bp-cMenu-"; // "breakpoints context menu"
+ let identifier = DebuggerController.Breakpoints.getIdentifier(attachment);
+ let enableSelfId = prefix + "enableSelf-" + identifier + "-menuitem";
+ let disableSelfId = prefix + "disableSelf-" + identifier + "-menuitem";
+ document.getElementById(enableSelfId).setAttribute("hidden", "true");
+ document.getElementById(disableSelfId).removeAttribute("hidden");
+
+ // Update the breakpoint toggle button checked state.
+ this._toggleBreakpointsButton.removeAttribute("checked");
+
+ // Update the checkbox state if necessary.
+ if (!aOptions.silent) {
+ attachment.view.checkbox.setAttribute("checked", "true");
+ }
+
+ return DebuggerController.Breakpoints.addBreakpoint(aLocation, {
+ // No need to update the pane, since this method is invoked because
+ // a breakpoint's view was interacted with.
+ noPaneUpdate: true
+ });
+ },
+
+ /**
+ * Disables a breakpoint.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ * @param object aOptions [optional]
+ * Additional options or flags supported by this operation:
+ * - silent: pass true to not update the checkbox checked state;
+ * this is usually necessary when the checked state will
+ * be updated automatically (e.g: on a checkbox click).
+ * @return object
+ * A promise that is resolved after the breakpoint is disabled, or
+ * rejected if no breakpoint was found at the specified location.
+ */
+ disableBreakpoint: function(aLocation, aOptions = {}) {
+ let breakpointItem = this.getBreakpoint(aLocation);
+ if (!breakpointItem) {
+ return promise.reject(new Error("No breakpoint found."));
+ }
+
+ // Breakpoint will now be disabled.
+ let attachment = breakpointItem.attachment;
+ attachment.disabled = true;
+
+ // Update the corresponding menu items to reflect the disabled state.
+ let prefix = "bp-cMenu-"; // "breakpoints context menu"
+ let identifier = DebuggerController.Breakpoints.getIdentifier(attachment);
+ let enableSelfId = prefix + "enableSelf-" + identifier + "-menuitem";
+ let disableSelfId = prefix + "disableSelf-" + identifier + "-menuitem";
+ document.getElementById(enableSelfId).removeAttribute("hidden");
+ document.getElementById(disableSelfId).setAttribute("hidden", "true");
+
+ // Update the checkbox state if necessary.
+ if (!aOptions.silent) {
+ attachment.view.checkbox.removeAttribute("checked");
+ }
+
+ return DebuggerController.Breakpoints.removeBreakpoint(aLocation, {
+ // No need to update this pane, since this method is invoked because
+ // a breakpoint's view was interacted with.
+ noPaneUpdate: true,
+ // Mark this breakpoint as being "disabled", not completely removed.
+ // This makes sure it will not be forgotten across target navigations.
+ rememberDisabled: true
+ });
+ },
+
+ /**
+ * Highlights a breakpoint in this sources container.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ * @param object aOptions [optional]
+ * An object containing some of the following boolean properties:
+ * - openPopup: tells if the expression popup should be shown.
+ * - noEditorUpdate: tells if you want to skip editor updates.
+ */
+ highlightBreakpoint: function(aLocation, aOptions = {}) {
+ let breakpointItem = this.getBreakpoint(aLocation);
+ if (!breakpointItem) {
+ return;
+ }
+
+ // Breakpoint will now be selected.
+ this._selectBreakpoint(breakpointItem);
+
+ // Update the editor location if necessary.
+ if (!aOptions.noEditorUpdate) {
+ DebuggerView.setEditorLocation(aLocation.actor, aLocation.line, { noDebug: true });
+ }
+
+ // If the breakpoint requires a new conditional expression, display
+ // the panel to input the corresponding expression.
+ if (aOptions.openPopup) {
+ this._openConditionalPopup();
+ } else {
+ this._hideConditionalPopup();
+ }
+ },
+
+ /**
+ * Highlight the breakpoint on the current currently focused line/column
+ * if it exists.
+ */
+ highlightBreakpointAtCursor: function() {
+ let actor = DebuggerView.Sources.selectedValue;
+ let line = DebuggerView.editor.getCursor().line + 1;
+
+ let location = { actor: actor, line: line };
+ this.highlightBreakpoint(location, { noEditorUpdate: true });
+ },
+
+ /**
+ * Unhighlights the current breakpoint in this sources container.
+ */
+ unhighlightBreakpoint: function() {
+ this._hideConditionalPopup();
+ this._unselectBreakpoint();
+ },
+
+ /**
+ * Update the checked/unchecked and enabled/disabled states of the buttons in
+ * the sources toolbar based on the currently selected source's state.
+ */
+ updateToolbarButtonsState: function() {
+ const { source } = this.selectedItem.attachment;
+ const sourceClient = gThreadClient.source(source);
+
+ if (sourceClient.isBlackBoxed) {
+ this._prettyPrintButton.setAttribute("disabled", true);
+ this._blackBoxButton.setAttribute("checked", true);
+ } else {
+ this._prettyPrintButton.removeAttribute("disabled");
+ this._blackBoxButton.removeAttribute("checked");
+ }
+
+ if (sourceClient.isPrettyPrinted) {
+ this._prettyPrintButton.setAttribute("checked", true);
+ } else {
+ this._prettyPrintButton.removeAttribute("checked");
+ }
+ },
+
+ /**
+ * Toggle the pretty printing of the selected source.
+ */
+ togglePrettyPrint: Task.async(function*() {
+ if (this._prettyPrintButton.hasAttribute("disabled")) {
+ return;
+ }
+
+ const resetEditor = ([{ actor }]) => {
+ // Only set the text when the source is still selected.
+ if (actor == this.selectedValue) {
+ DebuggerView.setEditorLocation(actor, 0, { force: true });
+ }
+ };
+
+ const printError = ([{ url }, error]) => {
+ DevToolsUtils.reportException("togglePrettyPrint", error);
+ };
+
+ DebuggerView.showProgressBar();
+ const { source } = this.selectedItem.attachment;
+ const sourceClient = gThreadClient.source(source);
+ const shouldPrettyPrint = !sourceClient.isPrettyPrinted;
+
+ if (shouldPrettyPrint) {
+ this._prettyPrintButton.setAttribute("checked", true);
+ } else {
+ this._prettyPrintButton.removeAttribute("checked");
+ }
+
+ try {
+ let resolution = yield DebuggerController.SourceScripts.togglePrettyPrint(source);
+ resetEditor(resolution);
+ } catch (rejection) {
+ printError(rejection);
+ }
+
+ DebuggerView.showEditor();
+ this.updateToolbarButtonsState();
+ }),
+
+ /**
+ * Toggle the black boxed state of the selected source.
+ */
+ toggleBlackBoxing: Task.async(function*() {
+ const { source } = this.selectedItem.attachment;
+ const sourceClient = gThreadClient.source(source);
+ const shouldBlackBox = !sourceClient.isBlackBoxed;
+
+ // Be optimistic that the (un-)black boxing will succeed, so enable/disable
+ // the pretty print button and check/uncheck the black box button immediately.
+ // Then, once we actually get the results from the server, make sure that
+ // it is in the correct state again by calling `updateToolbarButtonsState`.
+
+ if (shouldBlackBox) {
+ this._prettyPrintButton.setAttribute("disabled", true);
+ this._blackBoxButton.setAttribute("checked", true);
+ } else {
+ this._prettyPrintButton.removeAttribute("disabled");
+ this._blackBoxButton.removeAttribute("checked");
+ }
+
+ try {
+ yield DebuggerController.SourceScripts.setBlackBoxing(source, shouldBlackBox);
+ } catch (e) {
+ // Continue execution in this task even if blackboxing failed.
+ }
+
+ this.updateToolbarButtonsState();
+ }),
+
+ /**
+ * Toggles all breakpoints enabled/disabled.
+ */
+ toggleBreakpoints: function() {
+ let breakpoints = this.getAllBreakpoints();
+ let hasBreakpoints = breakpoints.length > 0;
+ let hasEnabledBreakpoints = breakpoints.some(e => !e.attachment.disabled);
+
+ if (hasBreakpoints && hasEnabledBreakpoints) {
+ this._toggleBreakpointsButton.setAttribute("checked", true);
+ this._onDisableAll();
+ } else {
+ this._toggleBreakpointsButton.removeAttribute("checked");
+ this._onEnableAll();
+ }
+ },
+
+ hidePrettyPrinting: function() {
+ this._prettyPrintButton.style.display = 'none';
+
+ if (this._blackBoxButton.style.display === 'none') {
+ let sep = document.querySelector('#sources-toolbar .devtools-separator');
+ sep.style.display = 'none';
+ }
+ },
+
+ hideBlackBoxing: function() {
+ this._blackBoxButton.style.display = 'none';
+
+ if (this._prettyPrintButton.style.display === 'none') {
+ let sep = document.querySelector('#sources-toolbar .devtools-separator');
+ sep.style.display = 'none';
+ }
+ },
+
+ /**
+ * Look up a source actor id for a location. This is necessary for
+ * backwards compatibility; otherwise we could just use the `actor`
+ * property. Older servers don't use the same actor ids for sources
+ * across reloads, so we resolve a url to the current actor if a url
+ * exists.
+ *
+ * @param object aLocation
+ * An object with the following properties:
+ * - actor: the source actor id
+ * - url: a url (might be null)
+ */
+ getActorForLocation: function(aLocation) {
+ if (aLocation.url) {
+ for (var item of this) {
+ let source = item.attachment.source;
+
+ if (aLocation.url === source.url) {
+ return source.actor;
+ }
+ }
+ }
+ return aLocation.actor;
+ },
+
+ /**
+ * Marks a breakpoint as selected in this sources container.
+ *
+ * @param object aItem
+ * The breakpoint item to select.
+ */
+ _selectBreakpoint: function(aItem) {
+ if (this._selectedBreakpointItem == aItem) {
+ return;
+ }
+ this._unselectBreakpoint();
+
+ this._selectedBreakpointItem = aItem;
+ this._selectedBreakpointItem.target.classList.add("selected");
+
+ // Ensure the currently selected breakpoint is visible.
+ this.widget.ensureElementIsVisible(aItem.target);
+ },
+
+ /**
+ * Marks the current breakpoint as unselected in this sources container.
+ */
+ _unselectBreakpoint: function() {
+ if (!this._selectedBreakpointItem) {
+ return;
+ }
+ this._selectedBreakpointItem.target.classList.remove("selected");
+ this._selectedBreakpointItem = null;
+ },
+
+ /**
+ * Opens a conditional breakpoint's expression input popup.
+ */
+ _openConditionalPopup: function() {
+ let breakpointItem = this._selectedBreakpointItem;
+ let attachment = breakpointItem.attachment;
+ // Check if this is an enabled conditional breakpoint, and if so,
+ // retrieve the current conditional epression.
+ let breakpointPromise = DebuggerController.Breakpoints._getAdded(attachment);
+ if (breakpointPromise) {
+ breakpointPromise.then(aBreakpointClient => {
+ let isConditionalBreakpoint = aBreakpointClient.hasCondition();
+ let condition = aBreakpointClient.getCondition();
+ doOpen.call(this, isConditionalBreakpoint ? condition : "")
+ });
+ } else {
+ doOpen.call(this, "")
+ }
+
+ function doOpen(aConditionalExpression) {
+ // Update the conditional expression textbox. If no expression was
+ // previously set, revert to using an empty string by default.
+ this._cbTextbox.value = aConditionalExpression;
+
+ // Show the conditional expression panel. The popup arrow should be pointing
+ // at the line number node in the breakpoint item view.
+ this._cbPanel.hidden = false;
+ this._cbPanel.openPopup(breakpointItem.attachment.view.lineNumber,
+ BREAKPOINT_CONDITIONAL_POPUP_POSITION,
+ BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X,
+ BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y);
+ }
+ },
+
+ /**
+ * Hides a conditional breakpoint's expression input popup.
+ */
+ _hideConditionalPopup: function() {
+ this._cbPanel.hidden = true;
+
+ // Sometimes this._cbPanel doesn't have hidePopup method which doesn't
+ // break anything but simply outputs an exception to the console.
+ if (this._cbPanel.hidePopup) {
+ this._cbPanel.hidePopup();
+ }
+ },
+
+ /**
+ * Customization function for creating a breakpoint item's UI.
+ *
+ * @param object aOptions
+ * A couple of options or flags supported by this operation:
+ * - location: the breakpoint's source location and line number
+ * - disabled: the breakpoint's disabled state, boolean
+ * - text: the breakpoint's line text to be displayed
+ * @return object
+ * An object containing the breakpoint container, checkbox,
+ * line number and line text nodes.
+ */
+ _createBreakpointView: function(aOptions) {
+ let { location, disabled, text } = aOptions;
+ let identifier = DebuggerController.Breakpoints.getIdentifier(location);
+
+ let checkbox = document.createElement("checkbox");
+ checkbox.setAttribute("checked", !disabled);
+ checkbox.className = "dbg-breakpoint-checkbox";
+
+ let lineNumberNode = document.createElement("label");
+ lineNumberNode.className = "plain dbg-breakpoint-line";
+ lineNumberNode.setAttribute("value", location.line);
+
+ let lineTextNode = document.createElement("label");
+ lineTextNode.className = "plain dbg-breakpoint-text";
+ lineTextNode.setAttribute("value", text);
+ lineTextNode.setAttribute("crop", "end");
+ lineTextNode.setAttribute("flex", "1");
+
+ let tooltip = text.substr(0, BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH);
+ lineTextNode.setAttribute("tooltiptext", tooltip);
+
+ let container = document.createElement("hbox");
+ container.id = "breakpoint-" + identifier;
+ container.className = "dbg-breakpoint side-menu-widget-item-other";
+ container.classList.add("devtools-monospace");
+ container.setAttribute("align", "center");
+ container.setAttribute("flex", "1");
+
+ container.addEventListener("click", this._onBreakpointClick, false);
+ checkbox.addEventListener("click", this._onBreakpointCheckboxClick, false);
+
+ container.appendChild(checkbox);
+ container.appendChild(lineNumberNode);
+ container.appendChild(lineTextNode);
+
+ return {
+ container: container,
+ checkbox: checkbox,
+ lineNumber: lineNumberNode,
+ lineText: lineTextNode
+ };
+ },
+
+ /**
+ * Creates a context menu for a breakpoint element.
+ *
+ * @param object aOptions
+ * A couple of options or flags supported by this operation:
+ * - location: the breakpoint's source location and line number
+ * - disabled: the breakpoint's disabled state, boolean
+ * @return object
+ * An object containing the breakpoint commandset and menu popup ids.
+ */
+ _createContextMenu: function(aOptions) {
+ let { location, disabled } = aOptions;
+ let identifier = DebuggerController.Breakpoints.getIdentifier(location);
+
+ let commandset = document.createElement("commandset");
+ let menupopup = document.createElement("menupopup");
+ commandset.id = "bp-cSet-" + identifier;
+ menupopup.id = "bp-mPop-" + identifier;
+
+ createMenuItem.call(this, "enableSelf", !disabled);
+ createMenuItem.call(this, "disableSelf", disabled);
+ createMenuItem.call(this, "deleteSelf");
+ createMenuSeparator();
+ createMenuItem.call(this, "setConditional");
+ createMenuSeparator();
+ createMenuItem.call(this, "enableOthers");
+ createMenuItem.call(this, "disableOthers");
+ createMenuItem.call(this, "deleteOthers");
+ createMenuSeparator();
+ createMenuItem.call(this, "enableAll");
+ createMenuItem.call(this, "disableAll");
+ createMenuSeparator();
+ createMenuItem.call(this, "deleteAll");
+
+ this._popupset.appendChild(menupopup);
+ this._commandset.appendChild(commandset);
+
+ return {
+ commandsetId: commandset.id,
+ menupopupId: menupopup.id
+ };
+
+ /**
+ * Creates a menu item specified by a name with the appropriate attributes
+ * (label and handler).
+ *
+ * @param string aName
+ * A global identifier for the menu item.
+ * @param boolean aHiddenFlag
+ * True if this menuitem should be hidden.
+ */
+ function createMenuItem(aName, aHiddenFlag) {
+ let menuitem = document.createElement("menuitem");
+ let command = document.createElement("command");
+
+ let prefix = "bp-cMenu-"; // "breakpoints context menu"
+ let commandId = prefix + aName + "-" + identifier + "-command";
+ let menuitemId = prefix + aName + "-" + identifier + "-menuitem";
+
+ let label = L10N.getStr("breakpointMenuItem." + aName);
+ let func = "_on" + aName.charAt(0).toUpperCase() + aName.slice(1);
+
+ command.id = commandId;
+ command.setAttribute("label", label);
+ command.addEventListener("command", () => this[func](location), false);
+
+ menuitem.id = menuitemId;
+ menuitem.setAttribute("command", commandId);
+ aHiddenFlag && menuitem.setAttribute("hidden", "true");
+
+ commandset.appendChild(command);
+ menupopup.appendChild(menuitem);
+ }
+
+ /**
+ * Creates a simple menu separator element and appends it to the current
+ * menupopup hierarchy.
+ */
+ function createMenuSeparator() {
+ let menuseparator = document.createElement("menuseparator");
+ menupopup.appendChild(menuseparator);
+ }
+ },
+
+ /**
+ * Function called each time a breakpoint item is removed.
+ *
+ * @param object aItem
+ * The corresponding item.
+ */
+ _onBreakpointRemoved: function(aItem) {
+ dumpn("Finalizing breakpoint item: " + aItem.stringify());
+
+ // Destroy the context menu for the breakpoint.
+ let contextMenu = aItem.attachment.popup;
+ document.getElementById(contextMenu.commandsetId).remove();
+ document.getElementById(contextMenu.menupopupId).remove();
+
+ // Clear the breakpoint selection.
+ if (this._selectedBreakpointItem == aItem) {
+ this._selectedBreakpointItem = null;
+ }
+ },
+
+ /**
+ * The load listener for the source editor.
+ */
+ _onEditorLoad: function(aName, aEditor) {
+ aEditor.on("cursorActivity", this._onEditorCursorActivity);
+ },
+
+ /**
+ * The unload listener for the source editor.
+ */
+ _onEditorUnload: function(aName, aEditor) {
+ aEditor.off("cursorActivity", this._onEditorCursorActivity);
+ },
+
+ /**
+ * The selection listener for the source editor.
+ */
+ _onEditorCursorActivity: function(e) {
+ let editor = DebuggerView.editor;
+ let start = editor.getCursor("start").line + 1;
+ let end = editor.getCursor().line + 1;
+ let actor = this.selectedValue;
+
+ let location = { actor: actor, line: start };
+
+ if (this.getBreakpoint(location) && start == end) {
+ this.highlightBreakpoint(location, { noEditorUpdate: true });
+ } else {
+ this.unhighlightBreakpoint();
+ }
+ },
+
+ /**
+ * The select listener for the sources container.
+ */
+ _onSourceSelect: Task.async(function*({ detail: sourceItem }) {
+ if (!sourceItem) {
+ return;
+ }
+ const { source } = sourceItem.attachment;
+ const sourceClient = gThreadClient.source(source);
+
+ // The container is not empty and an actual item was selected.
+ DebuggerView.setEditorLocation(sourceItem.value);
+
+ // Attempt to automatically pretty print minified source code.
+ if (Prefs.autoPrettyPrint && !sourceClient.isPrettyPrinted) {
+ let isMinified = yield SourceUtils.isMinified(sourceClient);
+ if (isMinified) {
+ this.togglePrettyPrint();
+ }
+ }
+
+ // Set window title. No need to split the url by " -> " here, because it was
+ // already sanitized when the source was added.
+ document.title = L10N.getFormatStr("DebuggerWindowScriptTitle",
+ sourceItem.attachment.source.url);
+
+ DebuggerView.maybeShowBlackBoxMessage();
+ this.updateToolbarButtonsState();
+ }),
+
+ /**
+ * The click listener for the "stop black boxing" button.
+ */
+ _onStopBlackBoxing: Task.async(function*() {
+ const { source } = this.selectedItem.attachment;
+
+ try {
+ yield DebuggerController.SourceScripts.setBlackBoxing(source, false);
+ } catch (e) {
+ // Continue execution in this task even if blackboxing failed.
+ }
+
+ this.updateToolbarButtonsState();
+ }),
+
+ /**
+ * The click listener for a breakpoint container.
+ */
+ _onBreakpointClick: function(e) {
+ let sourceItem = this.getItemForElement(e.target);
+ let breakpointItem = this.getItemForElement.call(sourceItem, e.target);
+ let attachment = breakpointItem.attachment;
+
+ // Check if this is an enabled conditional breakpoint.
+ let breakpointPromise = DebuggerController.Breakpoints._getAdded(attachment);
+ if (breakpointPromise) {
+ breakpointPromise.then(aBreakpointClient => {
+ doHighlight.call(this, aBreakpointClient.hasCondition());
+ });
+ } else {
+ doHighlight.call(this, false);
+ }
+
+ function doHighlight(aConditionalBreakpointFlag) {
+ // Highlight the breakpoint in this pane and in the editor.
+ this.highlightBreakpoint(attachment, {
+ // Don't show the conditional expression popup if this is not a
+ // conditional breakpoint, or the right mouse button was pressed (to
+ // avoid clashing the popup with the context menu).
+ openPopup: aConditionalBreakpointFlag && e.button == 0
+ });
+ }
+ },
+
+ /**
+ * The click listener for a breakpoint checkbox.
+ */
+ _onBreakpointCheckboxClick: function(e) {
+ let sourceItem = this.getItemForElement(e.target);
+ let breakpointItem = this.getItemForElement.call(sourceItem, e.target);
+ let attachment = breakpointItem.attachment;
+
+ // Toggle the breakpoint enabled or disabled.
+ this[attachment.disabled ? "enableBreakpoint" : "disableBreakpoint"](attachment, {
+ // Do this silently (don't update the checkbox checked state), since
+ // this listener is triggered because a checkbox was already clicked.
+ silent: true
+ });
+
+ // Don't update the editor location (avoid propagating into _onBreakpointClick).
+ e.preventDefault();
+ e.stopPropagation();
+ },
+
+ /**
+ * The popup showing listener for the breakpoints conditional expression panel.
+ */
+ _onConditionalPopupShowing: function() {
+ this._conditionalPopupVisible = true; // Used in tests.
+ window.emit(EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
+ },
+
+ /**
+ * The popup shown listener for the breakpoints conditional expression panel.
+ */
+ _onConditionalPopupShown: function() {
+ this._cbTextbox.focus();
+ this._cbTextbox.select();
+ },
+
+ /**
+ * The popup hiding listener for the breakpoints conditional expression panel.
+ */
+ _onConditionalPopupHiding: Task.async(function*() {
+ this._conditionalPopupVisible = false; // Used in tests.
+
+ let breakpointItem = this._selectedBreakpointItem;
+ let attachment = breakpointItem.attachment;
+
+ // Check if this is an enabled conditional breakpoint, and if so,
+ // save the current conditional epression.
+ let breakpointPromise = DebuggerController.Breakpoints._getAdded(attachment);
+ if (breakpointPromise) {
+ let { location } = yield breakpointPromise;
+ let condition = this._cbTextbox.value;
+ yield DebuggerController.Breakpoints.updateCondition(location, condition);
+ }
+
+ window.emit(EVENTS.CONDITIONAL_BREAKPOINT_POPUP_HIDING);
+ }),
+
+ /**
+ * The keypress listener for the breakpoints conditional expression textbox.
+ */
+ _onConditionalTextboxKeyPress: function(e) {
+ if (e.keyCode == e.DOM_VK_RETURN) {
+ this._hideConditionalPopup();
+ }
+ },
+
+ /**
+ * Called when the add breakpoint key sequence was pressed.
+ */
+ _onCmdAddBreakpoint: function(e) {
+ let actor = DebuggerView.Sources.selectedValue;
+ let line = (e && e.sourceEvent.target.tagName == 'menuitem' ?
+ DebuggerView.clickedLine + 1 :
+ DebuggerView.editor.getCursor().line + 1);
+ let location = { actor, line };
+ let breakpointItem = this.getBreakpoint(location);
+
+ // If a breakpoint already existed, remove it now.
+ if (breakpointItem) {
+ DebuggerController.Breakpoints.removeBreakpoint(location);
+ }
+ // No breakpoint existed at the required location, add one now.
+ else {
+ DebuggerController.Breakpoints.addBreakpoint(location);
+ }
+ },
+
+ /**
+ * Called when the add conditional breakpoint key sequence was pressed.
+ */
+ _onCmdAddConditionalBreakpoint: function(e) {
+ let actor = DebuggerView.Sources.selectedValue;
+ let line = (e && e.sourceEvent.target.tagName == 'menuitem' ?
+ DebuggerView.clickedLine + 1 :
+ DebuggerView.editor.getCursor().line + 1);
+ let location = { actor, line };
+ let breakpointItem = this.getBreakpoint(location);
+
+ // If a breakpoint already existed or wasn't a conditional, morph it now.
+ if (breakpointItem) {
+ this.highlightBreakpoint(location, { openPopup: true });
+ }
+ // No breakpoint existed at the required location, add one now.
+ else {
+ DebuggerController.Breakpoints.addBreakpoint(location, { openPopup: true });
+ }
+ },
+
+ /**
+ * Function invoked on the "setConditional" menuitem command.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _onSetConditional: function(aLocation) {
+ // Highlight the breakpoint and show a conditional expression popup.
+ this.highlightBreakpoint(aLocation, { openPopup: true });
+ },
+
+ /**
+ * Function invoked on the "enableSelf" menuitem command.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _onEnableSelf: function(aLocation) {
+ // Enable the breakpoint, in this container and the controller store.
+ this.enableBreakpoint(aLocation);
+ },
+
+ /**
+ * Function invoked on the "disableSelf" menuitem command.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _onDisableSelf: function(aLocation) {
+ // Disable the breakpoint, in this container and the controller store.
+ this.disableBreakpoint(aLocation);
+ },
+
+ /**
+ * Function invoked on the "deleteSelf" menuitem command.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _onDeleteSelf: function(aLocation) {
+ // Remove the breakpoint, from this container and the controller store.
+ this.removeBreakpoint(aLocation);
+ DebuggerController.Breakpoints.removeBreakpoint(aLocation);
+ },
+
+ /**
+ * Function invoked on the "enableOthers" menuitem command.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _onEnableOthers: function(aLocation) {
+ let enableOthers = aCallback => {
+ let other = this.getOtherBreakpoints(aLocation);
+ let outstanding = other.map(e => this.enableBreakpoint(e.attachment));
+ promise.all(outstanding).then(aCallback);
+ }
+
+ // Breakpoints can only be set while the debuggee is paused. To avoid
+ // an avalanche of pause/resume interrupts of the main thread, simply
+ // pause it beforehand if it's not already.
+ if (gThreadClient.state != "paused") {
+ gThreadClient.interrupt(() => enableOthers(() => gThreadClient.resume()));
+ } else {
+ enableOthers();
+ }
+ },
+
+ /**
+ * Function invoked on the "disableOthers" menuitem command.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _onDisableOthers: function(aLocation) {
+ let other = this.getOtherBreakpoints(aLocation);
+ other.forEach(e => this._onDisableSelf(e.attachment));
+ },
+
+ /**
+ * Function invoked on the "deleteOthers" menuitem command.
+ *
+ * @param object aLocation
+ * @see DebuggerController.Breakpoints.addBreakpoint
+ */
+ _onDeleteOthers: function(aLocation) {
+ let other = this.getOtherBreakpoints(aLocation);
+ other.forEach(e => this._onDeleteSelf(e.attachment));
+ },
+
+ /**
+ * Function invoked on the "enableAll" menuitem command.
+ */
+ _onEnableAll: function() {
+ this._onEnableOthers(undefined);
+ },
+
+ /**
+ * Function invoked on the "disableAll" menuitem command.
+ */
+ _onDisableAll: function() {
+ this._onDisableOthers(undefined);
+ },
+
+ /**
+ * Function invoked on the "deleteAll" menuitem command.
+ */
+ _onDeleteAll: function() {
+ this._onDeleteOthers(undefined);
+ },
+
+ _commandset: null,
+ _popupset: null,
+ _cmPopup: null,
+ _cbPanel: null,
+ _cbTextbox: null,
+ _selectedBreakpointItem: null,
+ _conditionalPopupVisible: false
+});
+
+/**
+ * Functions handling the traces UI.
+ */
+function TracerView() {
+ this._selectedItem = null;
+ this._matchingItems = null;
+ this.widget = null;
+
+ this._highlightItem = this._highlightItem.bind(this);
+ this._isNotSelectedItem = this._isNotSelectedItem.bind(this);
+
+ this._unhighlightMatchingItems =
+ DevToolsUtils.makeInfallible(this._unhighlightMatchingItems.bind(this));
+ this._onToggleTracing =
+ DevToolsUtils.makeInfallible(this._onToggleTracing.bind(this));
+ this._onStartTracing =
+ DevToolsUtils.makeInfallible(this._onStartTracing.bind(this));
+ this._onClear =
+ DevToolsUtils.makeInfallible(this._onClear.bind(this));
+ this._onSelect =
+ DevToolsUtils.makeInfallible(this._onSelect.bind(this));
+ this._onMouseOver =
+ DevToolsUtils.makeInfallible(this._onMouseOver.bind(this));
+ this._onSearch =
+ DevToolsUtils.makeInfallible(this._onSearch.bind(this));
+}
+
+TracerView.MAX_TRACES = 200;
+
+TracerView.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function() {
+ dumpn("Initializing the TracerView");
+
+ this._traceButton = document.getElementById("trace");
+ this._tracerTab = document.getElementById("tracer-tab");
+
+ // Remove tracer related elements from the dom and tear everything down if
+ // the tracer isn't enabled.
+ if (!Prefs.tracerEnabled) {
+ this._traceButton.remove();
+ this._traceButton = null;
+ this._tracerTab.remove();
+ this._tracerTab = null;
+ return;
+ }
+
+ this.widget = new FastListWidget(document.getElementById("tracer-traces"));
+ this._traceButton.removeAttribute("hidden");
+ this._tracerTab.removeAttribute("hidden");
+
+ this._search = document.getElementById("tracer-search");
+ this._template = document.getElementsByClassName("trace-item-template")[0];
+ this._templateItem = this._template.getElementsByClassName("trace-item")[0];
+ this._templateTypeIcon = this._template.getElementsByClassName("trace-type")[0];
+ this._templateNameNode = this._template.getElementsByClassName("trace-name")[0];
+
+ this.widget.addEventListener("select", this._onSelect, false);
+ this.widget.addEventListener("mouseover", this._onMouseOver, false);
+ this.widget.addEventListener("mouseout", this._unhighlightMatchingItems, false);
+ this._search.addEventListener("input", this._onSearch, false);
+
+ this._startTooltip = L10N.getStr("startTracingTooltip");
+ this._stopTooltip = L10N.getStr("stopTracingTooltip");
+ this._tracingNotStartedString = L10N.getStr("tracingNotStartedText");
+ this._noFunctionCallsString = L10N.getStr("noFunctionCallsText");
+
+ this._traceButton.setAttribute("tooltiptext", this._startTooltip);
+ this.emptyText = this._tracingNotStartedString;
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function() {
+ dumpn("Destroying the TracerView");
+
+ if (!this.widget) {
+ return;
+ }
+
+ this.widget.removeEventListener("select", this._onSelect, false);
+ this.widget.removeEventListener("mouseover", this._onMouseOver, false);
+ this.widget.removeEventListener("mouseout", this._unhighlightMatchingItems, false);
+ this._search.removeEventListener("input", this._onSearch, false);
+ },
+
+ /**
+ * Function invoked by the "toggleTracing" command to switch the tracer state.
+ */
+ _onToggleTracing: function() {
+ if (DebuggerController.Tracer.tracing) {
+ this._onStopTracing();
+ } else {
+ this._onStartTracing();
+ }
+ },
+
+ /**
+ * Function invoked either by the "startTracing" command or by
+ * _onToggleTracing to start execution tracing in the backend.
+ *
+ * @return object
+ * A promise resolved once the tracing has successfully started.
+ */
+ _onStartTracing: function() {
+ this._traceButton.setAttribute("checked", true);
+ this._traceButton.setAttribute("tooltiptext", this._stopTooltip);
+
+ this.empty();
+ this.emptyText = this._noFunctionCallsString;
+
+ let deferred = promise.defer();
+ DebuggerController.Tracer.startTracing(deferred.resolve);
+ return deferred.promise;
+ },
+
+ /**
+ * Function invoked by _onToggleTracing to stop execution tracing in the
+ * backend.
+ *
+ * @return object
+ * A promise resolved once the tracing has successfully stopped.
+ */
+ _onStopTracing: function() {
+ this._traceButton.removeAttribute("checked");
+ this._traceButton.setAttribute("tooltiptext", this._startTooltip);
+
+ this.emptyText = this._tracingNotStartedString;
+
+ let deferred = promise.defer();
+ DebuggerController.Tracer.stopTracing(deferred.resolve);
+ return deferred.promise;
+ },
+
+ /**
+ * Function invoked by the "clearTraces" command to empty the traces pane.
+ */
+ _onClear: function() {
+ this.empty();
+ },
+
+ /**
+ * Populate the given parent scope with the variable with the provided name
+ * and value.
+ *
+ * @param String aName
+ * The name of the variable.
+ * @param Object aParent
+ * The parent scope.
+ * @param Object aValue
+ * The value of the variable.
+ */
+ _populateVariable: function(aName, aParent, aValue) {
+ let item = aParent.addItem(aName, { value: aValue });
+
+ if (aValue) {
+ let wrappedValue = new DebuggerController.Tracer.WrappedObject(aValue);
+ DebuggerView.Variables.controller.populate(item, wrappedValue);
+ item.expand();
+ item.twisty = false;
+ }
+ },
+
+ /**
+ * Handler for the widget's "select" event. Displays parameters, exception, or
+ * return value depending on whether the selected trace is a call, throw, or
+ * return respectively.
+ *
+ * @param Object traceItem
+ * The selected trace item.
+ */
+ _onSelect: function _onSelect({ detail: traceItem }) {
+ if (!traceItem) {
+ return;
+ }
+
+ const data = traceItem.attachment.trace;
+ const { location: { url, line } } = data;
+ DebuggerView.setEditorLocation(
+ DebuggerView.Sources.getActorForLocation({ url }),
+ line,
+ { noDebug: true }
+ );
+
+ DebuggerView.Variables.empty();
+ const scope = DebuggerView.Variables.addScope();
+
+ if (data.type == "call") {
+ const params = DevToolsUtils.zip(data.parameterNames, data.arguments);
+ for (let [name, val] of params) {
+ if (val === undefined) {
+ scope.addItem(name, { value: "<value not available>" });
+ } else {
+ this._populateVariable(name, scope, val);
+ }
+ }
+ } else {
+ const varName = "<" + (data.type == "throw" ? "exception" : data.type) + ">";
+ this._populateVariable(varName, scope, data.returnVal);
+ }
+
+ scope.expand();
+ DebuggerView.showInstrumentsPane();
+ },
+
+ /**
+ * Add the hover frame enter/exit highlighting to a given item.
+ */
+ _highlightItem: function(aItem) {
+ if (!aItem || !aItem.target) {
+ return;
+ }
+ const trace = aItem.target.querySelector(".trace-item");
+ trace.classList.add("selected-matching");
+ },
+
+ /**
+ * Remove the hover frame enter/exit highlighting to a given item.
+ */
+ _unhighlightItem: function(aItem) {
+ if (!aItem || !aItem.target) {
+ return;
+ }
+ const match = aItem.target.querySelector(".selected-matching");
+ if (match) {
+ match.classList.remove("selected-matching");
+ }
+ },
+
+ /**
+ * Remove the frame enter/exit pair highlighting we do when hovering.
+ */
+ _unhighlightMatchingItems: function() {
+ if (this._matchingItems) {
+ this._matchingItems.forEach(this._unhighlightItem);
+ this._matchingItems = null;
+ }
+ },
+
+ /**
+ * Returns true if the given item is not the selected item.
+ */
+ _isNotSelectedItem: function(aItem) {
+ return aItem !== this.selectedItem;
+ },
+
+ /**
+ * Highlight the frame enter/exit pair of items for the given item.
+ */
+ _highlightMatchingItems: function(aItem) {
+ const frameId = aItem.attachment.trace.frameId;
+ const predicate = e => e.attachment.trace.frameId == frameId;
+
+ this._unhighlightMatchingItems();
+ this._matchingItems = this.items.filter(predicate);
+ this._matchingItems
+ .filter(this._isNotSelectedItem)
+ .forEach(this._highlightItem);
+ },
+
+ /**
+ * Listener for the mouseover event.
+ */
+ _onMouseOver: function({ target }) {
+ const traceItem = this.getItemForElement(target);
+ if (traceItem) {
+ this._highlightMatchingItems(traceItem);
+ }
+ },
+
+ /**
+ * Listener for typing in the search box.
+ */
+ _onSearch: function() {
+ const query = this._search.value.trim().toLowerCase();
+ const predicate = name => name.toLowerCase().contains(query);
+ this.filterContents(item => predicate(item.attachment.trace.name));
+ },
+
+ /**
+ * Select the traces tab in the sidebar.
+ */
+ selectTab: function() {
+ const tabs = this._tracerTab.parentElement;
+ tabs.selectedIndex = Array.indexOf(tabs.children, this._tracerTab);
+ },
+
+ /**
+ * Commit all staged items to the widget. Overridden so that we can call
+ * |FastListWidget.prototype.flush|.
+ */
+ commit: function() {
+ WidgetMethods.commit.call(this);
+ // TODO: Accessing non-standard widget properties. Figure out what's the
+ // best way to expose such things. Bug 895514.
+ this.widget.flush();
+ },
+
+ /**
+ * Adds the trace record provided as an argument to the view.
+ *
+ * @param object aTrace
+ * The trace record coming from the tracer actor.
+ */
+ addTrace: function(aTrace) {
+ // Create the element node for the trace item.
+ let view = this._createView(aTrace);
+
+ // Append a source item to this container.
+ this.push([view], {
+ staged: true,
+ attachment: {
+ trace: aTrace
+ }
+ });
+ },
+
+ /**
+ * Customization function for creating an item's UI.
+ *
+ * @return nsIDOMNode
+ * The network request view.
+ */
+ _createView: function(aTrace) {
+ let { type, name, location, blackBoxed, depth, frameId } = aTrace;
+ let { parameterNames, returnVal, arguments: args } = aTrace;
+ let fragment = document.createDocumentFragment();
+
+ this._templateItem.classList.toggle("black-boxed", blackBoxed);
+ this._templateItem.setAttribute("tooltiptext", SourceUtils.trimUrl(location.url));
+ this._templateItem.style.MozPaddingStart = depth + "em";
+
+ const TYPES = ["call", "yield", "return", "throw"];
+ for (let t of TYPES) {
+ this._templateTypeIcon.classList.toggle("trace-" + t, t == type);
+ }
+ this._templateTypeIcon.setAttribute("value", {
+ call: "\u2192",
+ yield: "Y",
+ return: "\u2190",
+ throw: "E",
+ terminated: "TERMINATED"
+ }[type]);
+
+ this._templateNameNode.setAttribute("value", name);
+
+ // All extra syntax and parameter nodes added.
+ const addedNodes = [];
+
+ if (parameterNames) {
+ const syntax = (p) => {
+ const el = document.createElement("label");
+ el.setAttribute("value", p);
+ el.classList.add("trace-syntax");
+ el.classList.add("plain");
+ addedNodes.push(el);
+ return el;
+ };
+
+ this._templateItem.appendChild(syntax("("));
+
+ for (let i = 0, n = parameterNames.length; i < n; i++) {
+ let param = document.createElement("label");
+ param.setAttribute("value", parameterNames[i]);
+ param.classList.add("trace-param");
+ param.classList.add("plain");
+ addedNodes.push(param);
+ this._templateItem.appendChild(param);
+
+ if (i + 1 !== n) {
+ this._templateItem.appendChild(syntax(", "));
+ }
+ }
+
+ this._templateItem.appendChild(syntax(")"));
+ }
+
+ // Flatten the DOM by removing one redundant box (the template container).
+ for (let node of this._template.childNodes) {
+ fragment.appendChild(node.cloneNode(true));
+ }
+
+ // Remove any added nodes from the template.
+ for (let node of addedNodes) {
+ this._templateItem.removeChild(node);
+ }
+
+ return fragment;
+ }
+});
+
+/**
+ * Utility functions for handling sources.
+ */
+let SourceUtils = {
+ _labelsCache: new Map(), // Can't use WeakMaps because keys are strings.
+ _groupsCache: new Map(),
+ _minifiedCache: new WeakMap(),
+
+ /**
+ * Returns true if the specified url and/or content type are specific to
+ * javascript files.
+ *
+ * @return boolean
+ * True if the source is likely javascript.
+ */
+ isJavaScript: function(aUrl, aContentType = "") {
+ return (aUrl && /\.jsm?$/.test(this.trimUrlQuery(aUrl))) ||
+ aContentType.contains("javascript");
+ },
+
+ /**
+ * Determines if the source text is minified by using
+ * the percentage indented of a subset of lines
+ *
+ * @return object
+ * A promise that resolves to true if source text is minified.
+ */
+ isMinified: Task.async(function*(sourceClient) {
+ if (this._minifiedCache.has(sourceClient)) {
+ return this._minifiedCache.get(sourceClient);
+ }
+
+ let [, text] = yield DebuggerController.SourceScripts.getText(sourceClient);
+ let isMinified;
+ let lineEndIndex = 0;
+ let lineStartIndex = 0;
+ let lines = 0;
+ let indentCount = 0;
+ let overCharLimit = false;
+
+ // Strip comments.
+ text = text.replace(/\/\*[\S\s]*?\*\/|\/\/(.+|\n)/g, "");
+
+ while (lines++ < SAMPLE_SIZE) {
+ lineEndIndex = text.indexOf("\n", lineStartIndex);
+ if (lineEndIndex == -1) {
+ break;
+ }
+ if (/^\s+/.test(text.slice(lineStartIndex, lineEndIndex))) {
+ indentCount++;
+ }
+ // For files with no indents but are not minified.
+ if ((lineEndIndex - lineStartIndex) > CHARACTER_LIMIT) {
+ overCharLimit = true;
+ break;
+ }
+ lineStartIndex = lineEndIndex + 1;
+ }
+
+ isMinified =
+ ((indentCount / lines) * 100) < INDENT_COUNT_THRESHOLD || overCharLimit;
+
+ this._minifiedCache.set(sourceClient, isMinified);
+ return isMinified;
+ }),
+
+ /**
+ * Clears the labels, groups and minify cache, populated by methods like
+ * SourceUtils.getSourceLabel or Source Utils.getSourceGroup.
+ * This should be done every time the content location changes.
+ */
+ clearCache: function() {
+ this._labelsCache.clear();
+ this._groupsCache.clear();
+ this._minifiedCache.clear();
+ },
+
+ /**
+ * Gets a unique, simplified label from a source url.
+ *
+ * @param string aUrl
+ * The source url.
+ * @return string
+ * The simplified label.
+ */
+ getSourceLabel: function(aUrl) {
+ let cachedLabel = this._labelsCache.get(aUrl);
+ if (cachedLabel) {
+ return cachedLabel;
+ }
+
+ let sourceLabel = null;
+
+ for (let name of Object.keys(KNOWN_SOURCE_GROUPS)) {
+ if (aUrl.startsWith(KNOWN_SOURCE_GROUPS[name])) {
+ sourceLabel = aUrl.substring(KNOWN_SOURCE_GROUPS[name].length);
+ }
+ }
+
+ if (!sourceLabel) {
+ sourceLabel = this.trimUrl(aUrl);
+ }
+
+ let unicodeLabel = NetworkHelper.convertToUnicode(unescape(sourceLabel));
+ this._labelsCache.set(aUrl, unicodeLabel);
+ return unicodeLabel;
+ },
+
+ /**
+ * Gets as much information as possible about the hostname and directory paths
+ * of an url to create a short url group identifier.
+ *
+ * @param string aUrl
+ * The source url.
+ * @return string
+ * The simplified group.
+ */
+ getSourceGroup: function(aUrl) {
+ let cachedGroup = this._groupsCache.get(aUrl);
+ if (cachedGroup) {
+ return cachedGroup;
+ }
+
+ try {
+ // Use an nsIURL to parse all the url path parts.
+ var uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
+ } catch (e) {
+ // This doesn't look like a url, or nsIURL can't handle it.
+ return "";
+ }
+
+ let groupLabel = uri.prePath;
+
+ for (let name of Object.keys(KNOWN_SOURCE_GROUPS)) {
+ if (aUrl.startsWith(KNOWN_SOURCE_GROUPS[name])) {
+ groupLabel = name;
+ }
+ }
+
+ let unicodeLabel = NetworkHelper.convertToUnicode(unescape(groupLabel));
+ this._groupsCache.set(aUrl, unicodeLabel)
+ return unicodeLabel;
+ },
+
+ /**
+ * Trims the url by shortening it if it exceeds a certain length, adding an
+ * ellipsis at the end.
+ *
+ * @param string aUrl
+ * The source url.
+ * @param number aLength [optional]
+ * The expected source url length.
+ * @param number aSection [optional]
+ * The section to trim. Supported values: "start", "center", "end"
+ * @return string
+ * The shortened url.
+ */
+ trimUrlLength: function(aUrl, aLength, aSection) {
+ aLength = aLength || SOURCE_URL_DEFAULT_MAX_LENGTH;
+ aSection = aSection || "end";
+
+ if (aUrl.length > aLength) {
+ switch (aSection) {
+ case "start":
+ return L10N.ellipsis + aUrl.slice(-aLength);
+ break;
+ case "center":
+ return aUrl.substr(0, aLength / 2 - 1) + L10N.ellipsis + aUrl.slice(-aLength / 2 + 1);
+ break;
+ case "end":
+ return aUrl.substr(0, aLength) + L10N.ellipsis;
+ break;
+ }
+ }
+ return aUrl;
+ },
+
+ /**
+ * Trims the query part or reference identifier of a url string, if necessary.
+ *
+ * @param string aUrl
+ * The source url.
+ * @return string
+ * The shortened url.
+ */
+ trimUrlQuery: function(aUrl) {
+ let length = aUrl.length;
+ let q1 = aUrl.indexOf('?');
+ let q2 = aUrl.indexOf('&');
+ let q3 = aUrl.indexOf('#');
+ let q = Math.min(q1 != -1 ? q1 : length,
+ q2 != -1 ? q2 : length,
+ q3 != -1 ? q3 : length);
+
+ return aUrl.slice(0, q);
+ },
+
+ /**
+ * Trims as much as possible from a url, while keeping the label unique
+ * in the sources container.
+ *
+ * @param string | nsIURL aUrl
+ * The source url.
+ * @param string aLabel [optional]
+ * The resulting label at each step.
+ * @param number aSeq [optional]
+ * The current iteration step.
+ * @return string
+ * The resulting label at the final step.
+ */
+ trimUrl: function(aUrl, aLabel, aSeq) {
+ if (!(aUrl instanceof Ci.nsIURL)) {
+ try {
+ // Use an nsIURL to parse all the url path parts.
+ aUrl = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
+ } catch (e) {
+ // This doesn't look like a url, or nsIURL can't handle it.
+ return aUrl;
+ }
+ }
+ if (!aSeq) {
+ let name = aUrl.fileName;
+ if (name) {
+ // This is a regular file url, get only the file name (contains the
+ // base name and extension if available).
+
+ // If this url contains an invalid query, unfortunately nsIURL thinks
+ // it's part of the file extension. It must be removed.
+ aLabel = aUrl.fileName.replace(/\&.*/, "");
+ } else {
+ // This is not a file url, hence there is no base name, nor extension.
+ // Proceed using other available information.
+ aLabel = "";
+ }
+ aSeq = 1;
+ }
+
+ // If we have a label and it doesn't only contain a query...
+ if (aLabel && aLabel.indexOf("?") != 0) {
+ // A page may contain multiple requests to the same url but with different
+ // queries. It is *not* redundant to show each one.
+ if (!DebuggerView.Sources.getItemForAttachment(e => e.label == aLabel)) {
+ return aLabel;
+ }
+ }
+
+ // Append the url query.
+ if (aSeq == 1) {
+ let query = aUrl.query;
+ if (query) {
+ return this.trimUrl(aUrl, aLabel + "?" + query, aSeq + 1);
+ }
+ aSeq++;
+ }
+ // Append the url reference.
+ if (aSeq == 2) {
+ let ref = aUrl.ref;
+ if (ref) {
+ return this.trimUrl(aUrl, aLabel + "#" + aUrl.ref, aSeq + 1);
+ }
+ aSeq++;
+ }
+ // Prepend the url directory.
+ if (aSeq == 3) {
+ let dir = aUrl.directory;
+ if (dir) {
+ return this.trimUrl(aUrl, dir.replace(/^\//, "") + aLabel, aSeq + 1);
+ }
+ aSeq++;
+ }
+ // Prepend the hostname and port number.
+ if (aSeq == 4) {
+ let host = aUrl.hostPort;
+ if (host) {
+ return this.trimUrl(aUrl, host + "/" + aLabel, aSeq + 1);
+ }
+ aSeq++;
+ }
+ // Use the whole url spec but ignoring the reference.
+ if (aSeq == 5) {
+ return this.trimUrl(aUrl, aUrl.specIgnoringRef, aSeq + 1);
+ }
+ // Give up.
+ return aUrl.spec;
+ }
+};
+
+/**
+ * Functions handling the variables bubble UI.
+ */
+function VariableBubbleView() {
+ dumpn("VariableBubbleView was instantiated");
+
+ this._onMouseMove = this._onMouseMove.bind(this);
+ this._onMouseOut = this._onMouseOut.bind(this);
+ this._onPopupHiding = this._onPopupHiding.bind(this);
+}
+
+VariableBubbleView.prototype = {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function() {
+ dumpn("Initializing the VariableBubbleView");
+
+ this._editorContainer = document.getElementById("editor");
+ this._editorContainer.addEventListener("mousemove", this._onMouseMove, false);
+ this._editorContainer.addEventListener("mouseout", this._onMouseOut, false);
+
+ this._tooltip = new Tooltip(document, {
+ closeOnEvents: [{
+ emitter: DebuggerController._toolbox,
+ event: "select"
+ }, {
+ emitter: this._editorContainer,
+ event: "scroll",
+ useCapture: true
+ }]
+ });
+ this._tooltip.defaultPosition = EDITOR_VARIABLE_POPUP_POSITION;
+ this._tooltip.defaultShowDelay = EDITOR_VARIABLE_HOVER_DELAY;
+ this._tooltip.panel.addEventListener("popuphiding", this._onPopupHiding);
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function() {
+ dumpn("Destroying the VariableBubbleView");
+
+ this._tooltip.panel.removeEventListener("popuphiding", this._onPopupHiding);
+ this._editorContainer.removeEventListener("mousemove", this._onMouseMove, false);
+ this._editorContainer.removeEventListener("mouseout", this._onMouseOut, false);
+ },
+
+ /**
+ * Specifies whether literals can be (redundantly) inspected in a popup.
+ * This behavior is deprecated, but still tested in a few places.
+ */
+ _ignoreLiterals: true,
+
+ /**
+ * Searches for an identifier underneath the specified position in the
+ * source editor, and if found, opens a VariablesView inspection popup.
+ *
+ * @param number x, y
+ * The left/top coordinates where to look for an identifier.
+ */
+ _findIdentifier: function(x, y) {
+ let editor = DebuggerView.editor;
+
+ // Calculate the editor's line and column at the current x and y coords.
+ let hoveredPos = editor.getPositionFromCoords({ left: x, top: y });
+ let hoveredOffset = editor.getOffset(hoveredPos);
+ let hoveredLine = hoveredPos.line;
+ let hoveredColumn = hoveredPos.ch;
+
+ // A source contains multiple scripts. Find the start index of the script
+ // containing the specified offset relative to its parent source.
+ let contents = editor.getText();
+ let location = DebuggerView.Sources.selectedValue;
+ let parsedSource = DebuggerController.Parser.get(contents, location);
+ let scriptInfo = parsedSource.getScriptInfo(hoveredOffset);
+
+ // If the script length is negative, we're not hovering JS source code.
+ if (scriptInfo.length == -1) {
+ return;
+ }
+
+ // Using the script offset, determine the actual line and column inside the
+ // script, to use when finding identifiers.
+ let scriptStart = editor.getPosition(scriptInfo.start);
+ let scriptLineOffset = scriptStart.line;
+ let scriptColumnOffset = (hoveredLine == scriptStart.line ? scriptStart.ch : 0);
+
+ let scriptLine = hoveredLine - scriptLineOffset;
+ let scriptColumn = hoveredColumn - scriptColumnOffset;
+ let identifierInfo = parsedSource.getIdentifierAt({
+ line: scriptLine + 1,
+ column: scriptColumn,
+ scriptIndex: scriptInfo.index,
+ ignoreLiterals: this._ignoreLiterals
+ });
+
+ // If the info is null, we're not hovering any identifier.
+ if (!identifierInfo) {
+ return;
+ }
+
+ // Transform the line and column relative to the parsed script back
+ // to the context of the parent source.
+ let { start: identifierStart, end: identifierEnd } = identifierInfo.location;
+ let identifierCoords = {
+ line: identifierStart.line + scriptLineOffset,
+ column: identifierStart.column + scriptColumnOffset,
+ length: identifierEnd.column - identifierStart.column
+ };
+
+ // Evaluate the identifier in the current stack frame and show the
+ // results in a VariablesView inspection popup.
+ DebuggerController.StackFrames.evaluate(identifierInfo.evalString)
+ .then(frameFinished => {
+ if ("return" in frameFinished) {
+ this.showContents({
+ coords: identifierCoords,
+ evalPrefix: identifierInfo.evalString,
+ objectActor: frameFinished.return
+ });
+ } else {
+ let msg = "Evaluation has thrown for: " + identifierInfo.evalString;
+ console.warn(msg);
+ dumpn(msg);
+ }
+ })
+ .then(null, err => {
+ let msg = "Couldn't evaluate: " + err.message;
+ console.error(msg);
+ dumpn(msg);
+ });
+ },
+
+ /**
+ * Shows an inspection popup for a specified object actor grip.
+ *
+ * @param string object
+ * An object containing the following properties:
+ * - coords: the inspected identifier coordinates in the editor,
+ * containing the { line, column, length } properties.
+ * - evalPrefix: a prefix for the variables view evaluation macros.
+ * - objectActor: the value grip for the object actor.
+ */
+ showContents: function({ coords, evalPrefix, objectActor }) {
+ let editor = DebuggerView.editor;
+ let { line, column, length } = coords;
+
+ // Highlight the function found at the mouse position.
+ this._markedText = editor.markText(
+ { line: line - 1, ch: column },
+ { line: line - 1, ch: column + length });
+
+ // If the grip represents a primitive value, use a more lightweight
+ // machinery to display it.
+ if (VariablesView.isPrimitive({ value: objectActor })) {
+ let className = VariablesView.getClass(objectActor);
+ let textContent = VariablesView.getString(objectActor);
+ this._tooltip.setTextContent({
+ messages: [textContent],
+ messagesClass: className,
+ containerClass: "plain"
+ }, [{
+ label: L10N.getStr('addWatchExpressionButton'),
+ className: "dbg-expression-button",
+ command: () => {
+ DebuggerView.VariableBubble.hideContents();
+ DebuggerView.WatchExpressions.addExpression(evalPrefix, true);
+ }
+ }]);
+ } else {
+ this._tooltip.setVariableContent(objectActor, {
+ searchPlaceholder: L10N.getStr("emptyPropertiesFilterText"),
+ searchEnabled: Prefs.variablesSearchboxVisible,
+ eval: (variable, value) => {
+ let string = variable.evaluationMacro(variable, value);
+ DebuggerController.StackFrames.evaluate(string);
+ DebuggerView.VariableBubble.hideContents();
+ }
+ }, {
+ getEnvironmentClient: aObject => gThreadClient.environment(aObject),
+ getObjectClient: aObject => gThreadClient.pauseGrip(aObject),
+ simpleValueEvalMacro: this._getSimpleValueEvalMacro(evalPrefix),
+ getterOrSetterEvalMacro: this._getGetterOrSetterEvalMacro(evalPrefix),
+ overrideValueEvalMacro: this._getOverrideValueEvalMacro(evalPrefix)
+ }, {
+ fetched: (aEvent, aType) => {
+ if (aType == "properties") {
+ window.emit(EVENTS.FETCHED_BUBBLE_PROPERTIES);
+ }
+ }
+ }, [{
+ label: L10N.getStr("addWatchExpressionButton"),
+ className: "dbg-expression-button",
+ command: () => {
+ DebuggerView.VariableBubble.hideContents();
+ DebuggerView.WatchExpressions.addExpression(evalPrefix, true);
+ }
+ }], DebuggerController._toolbox);
+ }
+
+ this._tooltip.show(this._markedText.anchor);
+ },
+
+ /**
+ * Hides the inspection popup.
+ */
+ hideContents: function() {
+ clearNamedTimeout("editor-mouse-move");
+ this._tooltip.hide();
+ },
+
+ /**
+ * Checks whether the inspection popup is shown.
+ *
+ * @return boolean
+ * True if the panel is shown or showing, false otherwise.
+ */
+ contentsShown: function() {
+ return this._tooltip.isShown();
+ },
+
+ /**
+ * Functions for getting customized variables view evaluation macros.
+ *
+ * @param string aPrefix
+ * See the corresponding VariablesView.* functions.
+ */
+ _getSimpleValueEvalMacro: function(aPrefix) {
+ return (item, string) =>
+ VariablesView.simpleValueEvalMacro(item, string, aPrefix);
+ },
+ _getGetterOrSetterEvalMacro: function(aPrefix) {
+ return (item, string) =>
+ VariablesView.getterOrSetterEvalMacro(item, string, aPrefix);
+ },
+ _getOverrideValueEvalMacro: function(aPrefix) {
+ return (item, string) =>
+ VariablesView.overrideValueEvalMacro(item, string, aPrefix);
+ },
+
+ /**
+ * The mousemove listener for the source editor.
+ */
+ _onMouseMove: function(e) {
+ // Prevent the variable inspection popup from showing when the thread client
+ // is not paused, or while a popup is already visible, or when the user tries
+ // to select text in the editor.
+ let isResumed = gThreadClient && gThreadClient.state != "paused";
+ let isSelecting = DebuggerView.editor.somethingSelected() && e.buttons > 0;
+ let isPopupVisible = !this._tooltip.isHidden();
+ if (isResumed || isSelecting || isPopupVisible) {
+ clearNamedTimeout("editor-mouse-move");
+ return;
+ }
+ // Allow events to settle down first. If the mouse hovers over
+ // a certain point in the editor long enough, try showing a variable bubble.
+ setNamedTimeout("editor-mouse-move",
+ EDITOR_VARIABLE_HOVER_DELAY, () => this._findIdentifier(e.clientX, e.clientY));
+ },
+
+ /**
+ * The mouseout listener for the source editor container node.
+ */
+ _onMouseOut: function() {
+ clearNamedTimeout("editor-mouse-move");
+ },
+
+ /**
+ * Listener handling the popup hiding event.
+ */
+ _onPopupHiding: function({ target }) {
+ if (this._tooltip.panel != target) {
+ return;
+ }
+ if (this._markedText) {
+ this._markedText.clear();
+ this._markedText = null;
+ }
+ if (!this._tooltip.isEmpty()) {
+ this._tooltip.empty();
+ }
+ },
+
+ _editorContainer: null,
+ _markedText: null,
+ _tooltip: null
+};
+
+/**
+ * Functions handling the watch expressions UI.
+ */
+function WatchExpressionsView() {
+ dumpn("WatchExpressionsView was instantiated");
+
+ this.switchExpression = this.switchExpression.bind(this);
+ this.deleteExpression = this.deleteExpression.bind(this);
+ this._createItemView = this._createItemView.bind(this);
+ this._onClick = this._onClick.bind(this);
+ this._onClose = this._onClose.bind(this);
+ this._onBlur = this._onBlur.bind(this);
+ this._onKeyPress = this._onKeyPress.bind(this);
+}
+
+WatchExpressionsView.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function() {
+ dumpn("Initializing the WatchExpressionsView");
+
+ this.widget = new SimpleListWidget(document.getElementById("expressions"));
+ this.widget.setAttribute("context", "debuggerWatchExpressionsContextMenu");
+ this.widget.addEventListener("click", this._onClick, false);
+
+ this.headerText = L10N.getStr("addWatchExpressionText");
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function() {
+ dumpn("Destroying the WatchExpressionsView");
+
+ this.widget.removeEventListener("click", this._onClick, false);
+ },
+
+ /**
+ * Adds a watch expression in this container.
+ *
+ * @param string aExpression [optional]
+ * An optional initial watch expression text.
+ * @param boolean aSkipUserInput [optional]
+ * Pass true to avoid waiting for additional user input
+ * on the watch expression.
+ */
+ addExpression: function(aExpression = "", aSkipUserInput = false) {
+ // Watch expressions are UI elements which benefit from visible panes.
+ DebuggerView.showInstrumentsPane();
+
+ // Create the element node for the watch expression item.
+ let itemView = this._createItemView(aExpression);
+
+ // Append a watch expression item to this container.
+ let expressionItem = this.push([itemView.container], {
+ index: 0, /* specifies on which position should the item be appended */
+ attachment: {
+ view: itemView,
+ initialExpression: aExpression,
+ currentExpression: "",
+ }
+ });
+
+ // Automatically focus the new watch expression input
+ // if additional user input is desired.
+ if (!aSkipUserInput) {
+ expressionItem.attachment.view.inputNode.select();
+ expressionItem.attachment.view.inputNode.focus();
+ DebuggerView.Variables.parentNode.scrollTop = 0;
+ }
+ // Otherwise, add and evaluate the new watch expression immediately.
+ else {
+ this.toggleContents(false);
+ this._onBlur({ target: expressionItem.attachment.view.inputNode });
+ }
+ },
+
+ /**
+ * Changes the watch expression corresponding to the specified variable item.
+ * This function is called whenever a watch expression's code is edited in
+ * the variables view container.
+ *
+ * @param Variable aVar
+ * The variable representing the watch expression evaluation.
+ * @param string aExpression
+ * The new watch expression text.
+ */
+ switchExpression: function(aVar, aExpression) {
+ let expressionItem =
+ [i for (i of this) if (i.attachment.currentExpression == aVar.name)][0];
+
+ // Remove the watch expression if it's going to be empty or a duplicate.
+ if (!aExpression || this.getAllStrings().indexOf(aExpression) != -1) {
+ this.deleteExpression(aVar);
+ return;
+ }
+
+ // Save the watch expression code string.
+ expressionItem.attachment.currentExpression = aExpression;
+ expressionItem.attachment.view.inputNode.value = aExpression;
+
+ // Synchronize with the controller's watch expressions store.
+ DebuggerController.StackFrames.syncWatchExpressions();
+ },
+
+ /**
+ * Removes the watch expression corresponding to the specified variable item.
+ * This function is called whenever a watch expression's value is edited in
+ * the variables view container.
+ *
+ * @param Variable aVar
+ * The variable representing the watch expression evaluation.
+ */
+ deleteExpression: function(aVar) {
+ let expressionItem =
+ [i for (i of this) if (i.attachment.currentExpression == aVar.name)][0];
+
+ // Remove the watch expression.
+ this.remove(expressionItem);
+
+ // Synchronize with the controller's watch expressions store.
+ DebuggerController.StackFrames.syncWatchExpressions();
+ },
+
+ /**
+ * Gets the watch expression code string for an item in this container.
+ *
+ * @param number aIndex
+ * The index used to identify the watch expression.
+ * @return string
+ * The watch expression code string.
+ */
+ getString: function(aIndex) {
+ return this.getItemAtIndex(aIndex).attachment.currentExpression;
+ },
+
+ /**
+ * Gets the watch expressions code strings for all items in this container.
+ *
+ * @return array
+ * The watch expressions code strings.
+ */
+ getAllStrings: function() {
+ return this.items.map(e => e.attachment.currentExpression);
+ },
+
+ /**
+ * Customization function for creating an item's UI.
+ *
+ * @param string aExpression
+ * The watch expression string.
+ */
+ _createItemView: function(aExpression) {
+ let container = document.createElement("hbox");
+ container.className = "list-widget-item dbg-expression";
+ container.setAttribute("align", "center");
+
+ let arrowNode = document.createElement("hbox");
+ arrowNode.className = "dbg-expression-arrow";
+
+ let inputNode = document.createElement("textbox");
+ inputNode.className = "plain dbg-expression-input devtools-monospace";
+ inputNode.setAttribute("value", aExpression);
+ inputNode.setAttribute("flex", "1");
+
+ let closeNode = document.createElement("toolbarbutton");
+ closeNode.className = "plain variables-view-delete";
+
+ closeNode.addEventListener("click", this._onClose, false);
+ inputNode.addEventListener("blur", this._onBlur, false);
+ inputNode.addEventListener("keypress", this._onKeyPress, false);
+
+ container.appendChild(arrowNode);
+ container.appendChild(inputNode);
+ container.appendChild(closeNode);
+
+ return {
+ container: container,
+ arrowNode: arrowNode,
+ inputNode: inputNode,
+ closeNode: closeNode
+ };
+ },
+
+ /**
+ * Called when the add watch expression key sequence was pressed.
+ */
+ _onCmdAddExpression: function(aText) {
+ // Only add a new expression if there's no pending input.
+ if (this.getAllStrings().indexOf("") == -1) {
+ this.addExpression(aText || DebuggerView.editor.getSelection());
+ }
+ },
+
+ /**
+ * Called when the remove all watch expressions key sequence was pressed.
+ */
+ _onCmdRemoveAllExpressions: function() {
+ // Empty the view of all the watch expressions and clear the cache.
+ this.empty();
+
+ // Synchronize with the controller's watch expressions store.
+ DebuggerController.StackFrames.syncWatchExpressions();
+ },
+
+ /**
+ * The click listener for this container.
+ */
+ _onClick: function(e) {
+ if (e.button != 0) {
+ // Only allow left-click to trigger this event.
+ return;
+ }
+ let expressionItem = this.getItemForElement(e.target);
+ if (!expressionItem) {
+ // The container is empty or we didn't click on an actual item.
+ this.addExpression();
+ }
+ },
+
+ /**
+ * The click listener for a watch expression's close button.
+ */
+ _onClose: function(e) {
+ // Remove the watch expression.
+ this.remove(this.getItemForElement(e.target));
+
+ // Synchronize with the controller's watch expressions store.
+ DebuggerController.StackFrames.syncWatchExpressions();
+
+ // Prevent clicking the expression element itself.
+ e.preventDefault();
+ e.stopPropagation();
+ },
+
+ /**
+ * The blur listener for a watch expression's textbox.
+ */
+ _onBlur: function({ target: textbox }) {
+ let expressionItem = this.getItemForElement(textbox);
+ let oldExpression = expressionItem.attachment.currentExpression;
+ let newExpression = textbox.value.trim();
+
+ // Remove the watch expression if it's empty.
+ if (!newExpression) {
+ this.remove(expressionItem);
+ }
+ // Remove the watch expression if it's a duplicate.
+ else if (!oldExpression && this.getAllStrings().indexOf(newExpression) != -1) {
+ this.remove(expressionItem);
+ }
+ // Expression is eligible.
+ else {
+ expressionItem.attachment.currentExpression = newExpression;
+ }
+
+ // Synchronize with the controller's watch expressions store.
+ DebuggerController.StackFrames.syncWatchExpressions();
+ },
+
+ /**
+ * The keypress listener for a watch expression's textbox.
+ */
+ _onKeyPress: function(e) {
+ switch (e.keyCode) {
+ case e.DOM_VK_RETURN:
+ case e.DOM_VK_ESCAPE:
+ e.stopPropagation();
+ DebuggerView.editor.focus();
+ }
+ }
+});
+
+/**
+ * Functions handling the event listeners UI.
+ */
+function EventListenersView() {
+ dumpn("EventListenersView was instantiated");
+
+ this._onCheck = this._onCheck.bind(this);
+ this._onClick = this._onClick.bind(this);
+}
+
+EventListenersView.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function() {
+ dumpn("Initializing the EventListenersView");
+
+ this.widget = new SideMenuWidget(document.getElementById("event-listeners"), {
+ showItemCheckboxes: true,
+ showGroupCheckboxes: true
+ });
+
+ this.emptyText = L10N.getStr("noEventListenersText");
+ this._eventCheckboxTooltip = L10N.getStr("eventCheckboxTooltip");
+ this._onSelectorString = " " + L10N.getStr("eventOnSelector") + " ";
+ this._inSourceString = " " + L10N.getStr("eventInSource") + " ";
+ this._inNativeCodeString = L10N.getStr("eventNative");
+
+ this.widget.addEventListener("check", this._onCheck, false);
+ this.widget.addEventListener("click", this._onClick, false);
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function() {
+ dumpn("Destroying the EventListenersView");
+
+ this.widget.removeEventListener("check", this._onCheck, false);
+ this.widget.removeEventListener("click", this._onClick, false);
+ },
+
+ /**
+ * Adds an event to this event listeners container.
+ *
+ * @param object aListener
+ * The listener object coming from the active thread.
+ * @param object aOptions [optional]
+ * Additional options for adding the source. Supported options:
+ * - staged: true to stage the item to be appended later
+ */
+ addListener: function(aListener, aOptions = {}) {
+ let { node: { selector }, function: { url }, type } = aListener;
+ if (!type) return;
+
+ // Some listener objects may be added from plugins, thus getting
+ // translated to native code.
+ if (!url) {
+ url = this._inNativeCodeString;
+ }
+
+ // If an event item for this listener's url and type was already added,
+ // avoid polluting the view and simply increase the "targets" count.
+ let eventItem = this.getItemForPredicate(aItem =>
+ aItem.attachment.url == url &&
+ aItem.attachment.type == type);
+
+ if (eventItem) {
+ let { selectors, view: { targets } } = eventItem.attachment;
+ if (selectors.indexOf(selector) == -1) {
+ selectors.push(selector);
+ targets.setAttribute("value", L10N.getFormatStr("eventNodes", selectors.length));
+ }
+ return;
+ }
+
+ // There's no easy way of grouping event types into higher-level groups,
+ // so we need to do this by hand.
+ let is = (...args) => args.indexOf(type) != -1;
+ let has = str => type.contains(str);
+ let starts = str => type.startsWith(str);
+ let group;
+
+ if (starts("animation")) {
+ group = L10N.getStr("animationEvents");
+ } else if (starts("audio")) {
+ group = L10N.getStr("audioEvents");
+ } else if (is("levelchange")) {
+ group = L10N.getStr("batteryEvents");
+ } else if (is("cut", "copy", "paste")) {
+ group = L10N.getStr("clipboardEvents");
+ } else if (starts("composition")) {
+ group = L10N.getStr("compositionEvents");
+ } else if (starts("device")) {
+ group = L10N.getStr("deviceEvents");
+ } else if (is("fullscreenchange", "fullscreenerror", "orientationchange",
+ "overflow", "resize", "scroll", "underflow", "zoom")) {
+ group = L10N.getStr("displayEvents");
+ } else if (starts("drag") || starts("drop")) {
+ group = L10N.getStr("dragAndDropEvents");
+ } else if (starts("gamepad")) {
+ group = L10N.getStr("gamepadEvents");
+ } else if (is("canplay", "canplaythrough", "durationchange", "emptied",
+ "ended", "loadeddata", "loadedmetadata", "pause", "play", "playing",
+ "ratechange", "seeked", "seeking", "stalled", "suspend", "timeupdate",
+ "volumechange", "waiting")) {
+ group = L10N.getStr("mediaEvents");
+ } else if (is("blocked", "complete", "success", "upgradeneeded", "versionchange")) {
+ group = L10N.getStr("indexedDBEvents");
+ } else if (is("blur", "change", "focus", "focusin", "focusout", "invalid",
+ "reset", "select", "submit")) {
+ group = L10N.getStr("interactionEvents");
+ } else if (starts("key") || is("input")) {
+ group = L10N.getStr("keyboardEvents");
+ } else if (starts("mouse") || has("click") || is("contextmenu", "show", "wheel")) {
+ group = L10N.getStr("mouseEvents");
+ } else if (starts("DOM")) {
+ group = L10N.getStr("mutationEvents");
+ } else if (is("abort", "error", "hashchange", "load", "loadend", "loadstart",
+ "pagehide", "pageshow", "progress", "timeout", "unload", "uploadprogress",
+ "visibilitychange")) {
+ group = L10N.getStr("navigationEvents");
+ } else if (is("pointerlockchange", "pointerlockerror")) {
+ group = L10N.getStr("pointerLockEvents");
+ } else if (is("compassneedscalibration", "userproximity")) {
+ group = L10N.getStr("sensorEvents");
+ } else if (starts("storage")) {
+ group = L10N.getStr("storageEvents");
+ } else if (is("beginEvent", "endEvent", "repeatEvent")) {
+ group = L10N.getStr("timeEvents");
+ } else if (starts("touch")) {
+ group = L10N.getStr("touchEvents");
+ } else {
+ group = L10N.getStr("otherEvents");
+ }
+
+ // Create the element node for the event listener item.
+ let itemView = this._createItemView(type, selector, url);
+
+ // Event breakpoints survive target navigations. Make sure the newly
+ // inserted event item is correctly checked.
+ let checkboxState =
+ DebuggerController.Breakpoints.DOM.activeEventNames.indexOf(type) != -1;
+
+ // Append an event listener item to this container.
+ this.push([itemView.container], {
+ staged: aOptions.staged, /* stage the item to be appended later? */
+ attachment: {
+ url: url,
+ type: type,
+ view: itemView,
+ selectors: [selector],
+ group: group,
+ checkboxState: checkboxState,
+ checkboxTooltip: this._eventCheckboxTooltip
+ }
+ });
+ },
+
+ /**
+ * Gets all the event types known to this container.
+ *
+ * @return array
+ * List of event types, for example ["load", "click"...]
+ */
+ getAllEvents: function() {
+ return this.attachments.map(e => e.type);
+ },
+
+ /**
+ * Gets the checked event types in this container.
+ *
+ * @return array
+ * List of event types, for example ["load", "click"...]
+ */
+ getCheckedEvents: function() {
+ return this.attachments.filter(e => e.checkboxState).map(e => e.type);
+ },
+
+ /**
+ * Customization function for creating an item's UI.
+ *
+ * @param string aType
+ * The event type, for example "click".
+ * @param string aSelector
+ * The target element's selector.
+ * @param string url
+ * The source url in which the event listener is located.
+ * @return object
+ * An object containing the event listener view nodes.
+ */
+ _createItemView: function(aType, aSelector, aUrl) {
+ let container = document.createElement("hbox");
+ container.className = "dbg-event-listener";
+
+ let eventType = document.createElement("label");
+ eventType.className = "plain dbg-event-listener-type";
+ eventType.setAttribute("value", aType);
+ container.appendChild(eventType);
+
+ let typeSeparator = document.createElement("label");
+ typeSeparator.className = "plain dbg-event-listener-separator";
+ typeSeparator.setAttribute("value", this._onSelectorString);
+ container.appendChild(typeSeparator);
+
+ let eventTargets = document.createElement("label");
+ eventTargets.className = "plain dbg-event-listener-targets";
+ eventTargets.setAttribute("value", aSelector);
+ container.appendChild(eventTargets);
+
+ let selectorSeparator = document.createElement("label");
+ selectorSeparator.className = "plain dbg-event-listener-separator";
+ selectorSeparator.setAttribute("value", this._inSourceString);
+ container.appendChild(selectorSeparator);
+
+ let eventLocation = document.createElement("label");
+ eventLocation.className = "plain dbg-event-listener-location";
+ eventLocation.setAttribute("value", SourceUtils.getSourceLabel(aUrl));
+ eventLocation.setAttribute("flex", "1");
+ eventLocation.setAttribute("crop", "center");
+ container.appendChild(eventLocation);
+
+ return {
+ container: container,
+ type: eventType,
+ targets: eventTargets,
+ location: eventLocation
+ };
+ },
+
+ /**
+ * The check listener for the event listeners container.
+ */
+ _onCheck: function({ detail: { description, checked }, target }) {
+ if (description == "item") {
+ this.getItemForElement(target).attachment.checkboxState = checked;
+ DebuggerController.Breakpoints.DOM.scheduleEventBreakpointsUpdate();
+ return;
+ }
+
+ // Check all the event items in this group.
+ this.items
+ .filter(e => e.attachment.group == description)
+ .forEach(e => this.callMethod("checkItem", e.target, checked));
+ },
+
+ /**
+ * The select listener for the event listeners container.
+ */
+ _onClick: function({ target }) {
+ // Changing the checkbox state is handled by the _onCheck event. Avoid
+ // handling that again in this click event, so pass in "noSiblings"
+ // when retrieving the target's item, to ignore the checkbox.
+ let eventItem = this.getItemForElement(target, { noSiblings: true });
+ if (eventItem) {
+ let newState = eventItem.attachment.checkboxState ^= 1;
+ this.callMethod("checkItem", eventItem.target, newState);
+ }
+ },
+
+ _eventCheckboxTooltip: "",
+ _onSelectorString: "",
+ _inSourceString: "",
+ _inNativeCodeString: ""
+});
+
+/**
+ * Functions handling the global search UI.
+ */
+function GlobalSearchView() {
+ dumpn("GlobalSearchView was instantiated");
+
+ this._onHeaderClick = this._onHeaderClick.bind(this);
+ this._onLineClick = this._onLineClick.bind(this);
+ this._onMatchClick = this._onMatchClick.bind(this);
+}
+
+GlobalSearchView.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function() {
+ dumpn("Initializing the GlobalSearchView");
+
+ this.widget = new SimpleListWidget(document.getElementById("globalsearch"));
+ this._splitter = document.querySelector("#globalsearch + .devtools-horizontal-splitter");
+
+ this.emptyText = L10N.getStr("noMatchingStringsText");
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function() {
+ dumpn("Destroying the GlobalSearchView");
+ },
+
+ /**
+ * Sets the results container hidden or visible. It's hidden by default.
+ * @param boolean aFlag
+ */
+ set hidden(aFlag) {
+ this.widget.setAttribute("hidden", aFlag);
+ this._splitter.setAttribute("hidden", aFlag);
+ },
+
+ /**
+ * Gets the visibility state of the global search container.
+ * @return boolean
+ */
+ get hidden()
+ this.widget.getAttribute("hidden") == "true" ||
+ this._splitter.getAttribute("hidden") == "true",
+
+ /**
+ * Hides and removes all items from this search container.
+ */
+ clearView: function() {
+ this.hidden = true;
+ this.empty();
+ },
+
+ /**
+ * Selects the next found item in this container.
+ * Does not change the currently focused node.
+ */
+ selectNext: function() {
+ let totalLineResults = LineResults.size();
+ if (!totalLineResults) {
+ return;
+ }
+ if (++this._currentlyFocusedMatch >= totalLineResults) {
+ this._currentlyFocusedMatch = 0;
+ }
+ this._onMatchClick({
+ target: LineResults.getElementAtIndex(this._currentlyFocusedMatch)
+ });
+ },
+
+ /**
+ * Selects the previously found item in this container.
+ * Does not change the currently focused node.
+ */
+ selectPrev: function() {
+ let totalLineResults = LineResults.size();
+ if (!totalLineResults) {
+ return;
+ }
+ if (--this._currentlyFocusedMatch < 0) {
+ this._currentlyFocusedMatch = totalLineResults - 1;
+ }
+ this._onMatchClick({
+ target: LineResults.getElementAtIndex(this._currentlyFocusedMatch)
+ });
+ },
+
+ /**
+ * Schedules searching for a string in all of the sources.
+ *
+ * @param string aToken
+ * The string to search for.
+ * @param number aWait
+ * The amount of milliseconds to wait until draining.
+ */
+ scheduleSearch: function(aToken, aWait) {
+ // The amount of time to wait for the requests to settle.
+ let maxDelay = GLOBAL_SEARCH_ACTION_MAX_DELAY;
+ let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
+
+ // Allow requests to settle down first.
+ setNamedTimeout("global-search", delay, () => {
+ // Start fetching as many sources as possible, then perform the search.
+ let actors = DebuggerView.Sources.values;
+ let sourcesFetched = DebuggerController.SourceScripts.getTextForSources(actors);
+ sourcesFetched.then(aSources => this._doSearch(aToken, aSources));
+ });
+ },
+
+ /**
+ * Finds string matches in all the sources stored in the controller's cache,
+ * and groups them by url and line number.
+ *
+ * @param string aToken
+ * The string to search for.
+ * @param array aSources
+ * An array of [url, text] tuples for each source.
+ */
+ _doSearch: function(aToken, aSources) {
+ // Don't continue filtering if the searched token is an empty string.
+ if (!aToken) {
+ this.clearView();
+ return;
+ }
+
+ // Search is not case sensitive, prepare the actual searched token.
+ let lowerCaseToken = aToken.toLowerCase();
+ let tokenLength = aToken.length;
+
+ // Create a Map containing search details for each source.
+ let globalResults = new GlobalResults();
+
+ // Search for the specified token in each source's text.
+ for (let [actor, text] of aSources) {
+ let item = DebuggerView.Sources.getItemByValue(actor);
+ let url = item.attachment.source.url;
+ if (!url) {
+ continue;
+ }
+
+ // Verify that the search token is found anywhere in the source.
+ if (!text.toLowerCase().contains(lowerCaseToken)) {
+ continue;
+ }
+ // ...and if so, create a Map containing search details for each line.
+ let sourceResults = new SourceResults(actor, globalResults);
+
+ // Search for the specified token in each line's text.
+ text.split("\n").forEach((aString, aLine) => {
+ // Search is not case sensitive, prepare the actual searched line.
+ let lowerCaseLine = aString.toLowerCase();
+
+ // Verify that the search token is found anywhere in this line.
+ if (!lowerCaseLine.contains(lowerCaseToken)) {
+ return;
+ }
+ // ...and if so, create a Map containing search details for each word.
+ let lineResults = new LineResults(aLine, sourceResults);
+
+ // Search for the specified token this line's text.
+ lowerCaseLine.split(lowerCaseToken).reduce((aPrev, aCurr, aIndex, aArray) => {
+ let prevLength = aPrev.length;
+ let currLength = aCurr.length;
+
+ // Everything before the token is unmatched.
+ let unmatched = aString.substr(prevLength, currLength);
+ lineResults.add(unmatched);
+
+ // The lowered-case line was split by the lowered-case token. So,
+ // get the actual matched text from the original line's text.
+ if (aIndex != aArray.length - 1) {
+ let matched = aString.substr(prevLength + currLength, tokenLength);
+ let range = { start: prevLength + currLength, length: matched.length };
+ lineResults.add(matched, range, true);
+ }
+
+ // Continue with the next sub-region in this line's text.
+ return aPrev + aToken + aCurr;
+ }, "");
+
+ if (lineResults.matchCount) {
+ sourceResults.add(lineResults);
+ }
+ });
+
+ if (sourceResults.matchCount) {
+ globalResults.add(sourceResults);
+ }
+ }
+
+ // Rebuild the results, then signal if there are any matches.
+ if (globalResults.matchCount) {
+ this.hidden = false;
+ this._currentlyFocusedMatch = -1;
+ this._createGlobalResultsUI(globalResults);
+ window.emit(EVENTS.GLOBAL_SEARCH_MATCH_FOUND);
+ } else {
+ window.emit(EVENTS.GLOBAL_SEARCH_MATCH_NOT_FOUND);
+ }
+ },
+
+ /**
+ * Creates global search results entries and adds them to this container.
+ *
+ * @param GlobalResults aGlobalResults
+ * An object containing all source results, grouped by source location.
+ */
+ _createGlobalResultsUI: function(aGlobalResults) {
+ let i = 0;
+
+ for (let sourceResults of aGlobalResults) {
+ if (i++ == 0) {
+ this._createSourceResultsUI(sourceResults);
+ } else {
+ // Dispatch subsequent document manipulation operations, to avoid
+ // blocking the main thread when a large number of search results
+ // is found, thus giving the impression of faster searching.
+ Services.tm.currentThread.dispatch({ run:
+ this._createSourceResultsUI.bind(this, sourceResults)
+ }, 0);
+ }
+ }
+ },
+
+ /**
+ * Creates source search results entries and adds them to this container.
+ *
+ * @param SourceResults aSourceResults
+ * An object containing all the matched lines for a specific source.
+ */
+ _createSourceResultsUI: function(aSourceResults) {
+ // Create the element node for the source results item.
+ let container = document.createElement("hbox");
+ aSourceResults.createView(container, {
+ onHeaderClick: this._onHeaderClick,
+ onLineClick: this._onLineClick,
+ onMatchClick: this._onMatchClick
+ });
+
+ // Append a source results item to this container.
+ let item = this.push([container], {
+ index: -1, /* specifies on which position should the item be appended */
+ attachment: {
+ sourceResults: aSourceResults
+ }
+ });
+ },
+
+ /**
+ * The click listener for a results header.
+ */
+ _onHeaderClick: function(e) {
+ let sourceResultsItem = SourceResults.getItemForElement(e.target);
+ sourceResultsItem.instance.toggle(e);
+ },
+
+ /**
+ * The click listener for a results line.
+ */
+ _onLineClick: function(e) {
+ let lineResultsItem = LineResults.getItemForElement(e.target);
+ this._onMatchClick({ target: lineResultsItem.firstMatch });
+ },
+
+ /**
+ * The click listener for a result match.
+ */
+ _onMatchClick: function(e) {
+ if (e instanceof Event) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
+ let target = e.target;
+ let sourceResultsItem = SourceResults.getItemForElement(target);
+ let lineResultsItem = LineResults.getItemForElement(target);
+
+ sourceResultsItem.instance.expand();
+ this._currentlyFocusedMatch = LineResults.indexOfElement(target);
+ this._scrollMatchIntoViewIfNeeded(target);
+ this._bounceMatch(target);
+
+ let actor = sourceResultsItem.instance.actor;
+ let line = lineResultsItem.instance.line;
+
+ DebuggerView.setEditorLocation(actor, line + 1, { noDebug: true });
+
+ let range = lineResultsItem.lineData.range;
+ let cursor = DebuggerView.editor.getOffset({ line: line, ch: 0 });
+ let [ anchor, head ] = DebuggerView.editor.getPosition(
+ cursor + range.start,
+ cursor + range.start + range.length
+ );
+
+ DebuggerView.editor.setSelection(anchor, head);
+ },
+
+ /**
+ * Scrolls a match into view if not already visible.
+ *
+ * @param nsIDOMNode aMatch
+ * The match to scroll into view.
+ */
+ _scrollMatchIntoViewIfNeeded: function(aMatch) {
+ this.widget.ensureElementIsVisible(aMatch);
+ },
+
+ /**
+ * Starts a bounce animation for a match.
+ *
+ * @param nsIDOMNode aMatch
+ * The match to start a bounce animation for.
+ */
+ _bounceMatch: function(aMatch) {
+ Services.tm.currentThread.dispatch({ run: () => {
+ aMatch.addEventListener("transitionend", function onEvent() {
+ aMatch.removeEventListener("transitionend", onEvent);
+ aMatch.removeAttribute("focused");
+ });
+ aMatch.setAttribute("focused", "");
+ }}, 0);
+ aMatch.setAttribute("focusing", "");
+ },
+
+ _splitter: null,
+ _currentlyFocusedMatch: -1,
+ _forceExpandResults: false
+});
+
+/**
+ * An object containing all source results, grouped by source location.
+ * Iterable via "for (let [location, sourceResults] of globalResults) { }".
+ */
+function GlobalResults() {
+ this._store = [];
+ SourceResults._itemsByElement = new Map();
+ LineResults._itemsByElement = new Map();
+}
+
+GlobalResults.prototype = {
+ /**
+ * Adds source results to this store.
+ *
+ * @param SourceResults aSourceResults
+ * An object containing search results for a specific source.
+ */
+ add: function(aSourceResults) {
+ this._store.push(aSourceResults);
+ },
+
+ /**
+ * Gets the number of source results in this store.
+ */
+ get matchCount() this._store.length
+};
+
+/**
+ * An object containing all the matched lines for a specific source.
+ * Iterable via "for (let [lineNumber, lineResults] of sourceResults) { }".
+ *
+ * @param string aActor
+ * The target source actor id.
+ * @param GlobalResults aGlobalResults
+ * An object containing all source results, grouped by source location.
+ */
+function SourceResults(aActor, aGlobalResults) {
+ let item = DebuggerView.Sources.getItemByValue(aActor);
+ this.actor = aActor;
+ this.label = item.attachment.source.url;
+ this._globalResults = aGlobalResults;
+ this._store = [];
+}
+
+SourceResults.prototype = {
+ /**
+ * Adds line results to this store.
+ *
+ * @param LineResults aLineResults
+ * An object containing search results for a specific line.
+ */
+ add: function(aLineResults) {
+ this._store.push(aLineResults);
+ },
+
+ /**
+ * Gets the number of line results in this store.
+ */
+ get matchCount() this._store.length,
+
+ /**
+ * Expands the element, showing all the added details.
+ */
+ expand: function() {
+ this._resultsContainer.removeAttribute("hidden");
+ this._arrow.setAttribute("open", "");
+ },
+
+ /**
+ * Collapses the element, hiding all the added details.
+ */
+ collapse: function() {
+ this._resultsContainer.setAttribute("hidden", "true");
+ this._arrow.removeAttribute("open");
+ },
+
+ /**
+ * Toggles between the element collapse/expand state.
+ */
+ toggle: function(e) {
+ this.expanded ^= 1;
+ },
+
+ /**
+ * Gets this element's expanded state.
+ * @return boolean
+ */
+ get expanded()
+ this._resultsContainer.getAttribute("hidden") != "true" &&
+ this._arrow.hasAttribute("open"),
+
+ /**
+ * Sets this element's expanded state.
+ * @param boolean aFlag
+ */
+ set expanded(aFlag) this[aFlag ? "expand" : "collapse"](),
+
+ /**
+ * Gets the element associated with this item.
+ * @return nsIDOMNode
+ */
+ get target() this._target,
+
+ /**
+ * Customization function for creating this item's UI.
+ *
+ * @param nsIDOMNode aElementNode
+ * The element associated with the displayed item.
+ * @param object aCallbacks
+ * An object containing all the necessary callback functions:
+ * - onHeaderClick
+ * - onMatchClick
+ */
+ createView: function(aElementNode, aCallbacks) {
+ this._target = aElementNode;
+
+ let arrow = this._arrow = document.createElement("box");
+ arrow.className = "arrow";
+
+ let locationNode = document.createElement("label");
+ locationNode.className = "plain dbg-results-header-location";
+ locationNode.setAttribute("value", this.label);
+
+ let matchCountNode = document.createElement("label");
+ matchCountNode.className = "plain dbg-results-header-match-count";
+ matchCountNode.setAttribute("value", "(" + this.matchCount + ")");
+
+ let resultsHeader = this._resultsHeader = document.createElement("hbox");
+ resultsHeader.className = "dbg-results-header";
+ resultsHeader.setAttribute("align", "center")
+ resultsHeader.appendChild(arrow);
+ resultsHeader.appendChild(locationNode);
+ resultsHeader.appendChild(matchCountNode);
+ resultsHeader.addEventListener("click", aCallbacks.onHeaderClick, false);
+
+ let resultsContainer = this._resultsContainer = document.createElement("vbox");
+ resultsContainer.className = "dbg-results-container";
+ resultsContainer.setAttribute("hidden", "true");
+
+ // Create lines search results entries and add them to this container.
+ // Afterwards, if the number of matches is reasonable, expand this
+ // container automatically.
+ for (let lineResults of this._store) {
+ lineResults.createView(resultsContainer, aCallbacks);
+ }
+ if (this.matchCount < GLOBAL_SEARCH_EXPAND_MAX_RESULTS) {
+ this.expand();
+ }
+
+ let resultsBox = document.createElement("vbox");
+ resultsBox.setAttribute("flex", "1");
+ resultsBox.appendChild(resultsHeader);
+ resultsBox.appendChild(resultsContainer);
+
+ aElementNode.id = "source-results-" + this.actor;
+ aElementNode.className = "dbg-source-results";
+ aElementNode.appendChild(resultsBox);
+
+ SourceResults._itemsByElement.set(aElementNode, { instance: this });
+ },
+
+ actor: "",
+ _globalResults: null,
+ _store: null,
+ _target: null,
+ _arrow: null,
+ _resultsHeader: null,
+ _resultsContainer: null
+};
+
+/**
+ * An object containing all the matches for a specific line.
+ * Iterable via "for (let chunk of lineResults) { }".
+ *
+ * @param number aLine
+ * The target line in the source.
+ * @param SourceResults aSourceResults
+ * An object containing all the matched lines for a specific source.
+ */
+function LineResults(aLine, aSourceResults) {
+ this.line = aLine;
+ this._sourceResults = aSourceResults;
+ this._store = [];
+ this._matchCount = 0;
+}
+
+LineResults.prototype = {
+ /**
+ * Adds string details to this store.
+ *
+ * @param string aString
+ * The text contents chunk in the line.
+ * @param object aRange
+ * An object containing the { start, length } of the chunk.
+ * @param boolean aMatchFlag
+ * True if the chunk is a matched string, false if just text content.
+ */
+ add: function(aString, aRange, aMatchFlag) {
+ this._store.push({ string: aString, range: aRange, match: !!aMatchFlag });
+ this._matchCount += aMatchFlag ? 1 : 0;
+ },
+
+ /**
+ * Gets the number of word results in this store.
+ */
+ get matchCount() this._matchCount,
+
+ /**
+ * Gets the element associated with this item.
+ * @return nsIDOMNode
+ */
+ get target() this._target,
+
+ /**
+ * Customization function for creating this item's UI.
+ *
+ * @param nsIDOMNode aElementNode
+ * The element associated with the displayed item.
+ * @param object aCallbacks
+ * An object containing all the necessary callback functions:
+ * - onMatchClick
+ * - onLineClick
+ */
+ createView: function(aElementNode, aCallbacks) {
+ this._target = aElementNode;
+
+ let lineNumberNode = document.createElement("label");
+ lineNumberNode.className = "plain dbg-results-line-number";
+ lineNumberNode.classList.add("devtools-monospace");
+ lineNumberNode.setAttribute("value", this.line + 1);
+
+ let lineContentsNode = document.createElement("hbox");
+ lineContentsNode.className = "dbg-results-line-contents";
+ lineContentsNode.classList.add("devtools-monospace");
+ lineContentsNode.setAttribute("flex", "1");
+
+ let lineString = "";
+ let lineLength = 0;
+ let firstMatch = null;
+
+ for (let lineChunk of this._store) {
+ let { string, range, match } = lineChunk;
+ lineString = string.substr(0, GLOBAL_SEARCH_LINE_MAX_LENGTH - lineLength);
+ lineLength += string.length;
+
+ let lineChunkNode = document.createElement("label");
+ lineChunkNode.className = "plain dbg-results-line-contents-string";
+ lineChunkNode.setAttribute("value", lineString);
+ lineChunkNode.setAttribute("match", match);
+ lineContentsNode.appendChild(lineChunkNode);
+
+ if (match) {
+ this._entangleMatch(lineChunkNode, lineChunk);
+ lineChunkNode.addEventListener("click", aCallbacks.onMatchClick, false);
+ firstMatch = firstMatch || lineChunkNode;
+ }
+ if (lineLength >= GLOBAL_SEARCH_LINE_MAX_LENGTH) {
+ lineContentsNode.appendChild(this._ellipsis.cloneNode(true));
+ break;
+ }
+ }
+
+ this._entangleLine(lineContentsNode, firstMatch);
+ lineContentsNode.addEventListener("click", aCallbacks.onLineClick, false);
+
+ let searchResult = document.createElement("hbox");
+ searchResult.className = "dbg-search-result";
+ searchResult.appendChild(lineNumberNode);
+ searchResult.appendChild(lineContentsNode);
+
+ aElementNode.appendChild(searchResult);
+ },
+
+ /**
+ * Handles a match while creating the view.
+ * @param nsIDOMNode aNode
+ * @param object aMatchChunk
+ */
+ _entangleMatch: function(aNode, aMatchChunk) {
+ LineResults._itemsByElement.set(aNode, {
+ instance: this,
+ lineData: aMatchChunk
+ });
+ },
+
+ /**
+ * Handles a line while creating the view.
+ * @param nsIDOMNode aNode
+ * @param nsIDOMNode aFirstMatch
+ */
+ _entangleLine: function(aNode, aFirstMatch) {
+ LineResults._itemsByElement.set(aNode, {
+ instance: this,
+ firstMatch: aFirstMatch,
+ ignored: true
+ });
+ },
+
+ /**
+ * An nsIDOMNode label with an ellipsis value.
+ */
+ _ellipsis: (function() {
+ let label = document.createElement("label");
+ label.className = "plain dbg-results-line-contents-string";
+ label.setAttribute("value", L10N.ellipsis);
+ return label;
+ })(),
+
+ line: 0,
+ _sourceResults: null,
+ _store: null,
+ _target: null
+};
+
+/**
+ * A generator-iterator over the global, source or line results.
+ */
+GlobalResults.prototype[Symbol.iterator] =
+SourceResults.prototype[Symbol.iterator] =
+LineResults.prototype[Symbol.iterator] = function*() {
+ yield* this._store;
+};
+
+/**
+ * Gets the item associated with the specified element.
+ *
+ * @param nsIDOMNode aElement
+ * The element used to identify the item.
+ * @return object
+ * The matched item, or null if nothing is found.
+ */
+SourceResults.getItemForElement =
+LineResults.getItemForElement = function(aElement) {
+ return WidgetMethods.getItemForElement.call(this, aElement, { noSiblings: true });
+};
+
+/**
+ * Gets the element associated with a particular item at a specified index.
+ *
+ * @param number aIndex
+ * The index used to identify the item.
+ * @return nsIDOMNode
+ * The matched element, or null if nothing is found.
+ */
+SourceResults.getElementAtIndex =
+LineResults.getElementAtIndex = function(aIndex) {
+ for (let [element, item] of this._itemsByElement) {
+ if (!item.ignored && !aIndex--) {
+ return element;
+ }
+ }
+ return null;
+};
+
+/**
+ * Gets the index of an item associated with the specified element.
+ *
+ * @param nsIDOMNode aElement
+ * The element to get the index for.
+ * @return number
+ * The index of the matched element, or -1 if nothing is found.
+ */
+SourceResults.indexOfElement =
+LineResults.indexOfElement = function(aElement) {
+ let count = 0;
+ for (let [element, item] of this._itemsByElement) {
+ if (element == aElement) {
+ return count;
+ }
+ if (!item.ignored) {
+ count++;
+ }
+ }
+ return -1;
+};
+
+/**
+ * Gets the number of cached items associated with a specified element.
+ *
+ * @return number
+ * The number of key/value pairs in the corresponding map.
+ */
+SourceResults.size =
+LineResults.size = function() {
+ let count = 0;
+ for (let [, item] of this._itemsByElement) {
+ if (!item.ignored) {
+ count++;
+ }
+ }
+ return count;
+};
+
+/**
+ * Preliminary setup for the DebuggerView object.
+ */
+DebuggerView.Sources = new SourcesView();
+DebuggerView.VariableBubble = new VariableBubbleView();
+DebuggerView.Tracer = new TracerView();
+DebuggerView.WatchExpressions = new WatchExpressionsView();
+DebuggerView.EventListeners = new EventListenersView();
+DebuggerView.GlobalSearch = new GlobalSearchView();
diff --git a/toolkit/devtools/debugger/debugger-toolbar.js b/toolkit/devtools/debugger/debugger-toolbar.js
new file mode 100644
index 000000000..695bbd68d
--- /dev/null
+++ b/toolkit/devtools/debugger/debugger-toolbar.js
@@ -0,0 +1,1594 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// A time interval sufficient for the options popup panel to finish hiding
+// itself.
+const POPUP_HIDDEN_DELAY = 100; // ms
+
+/**
+ * Functions handling the toolbar view: close button, expand/collapse button,
+ * pause/resume and stepping buttons etc.
+ */
+function ToolbarView() {
+ dumpn("ToolbarView was instantiated");
+
+ this._onTogglePanesPressed = this._onTogglePanesPressed.bind(this);
+ this._onResumePressed = this._onResumePressed.bind(this);
+ this._onStepOverPressed = this._onStepOverPressed.bind(this);
+ this._onStepInPressed = this._onStepInPressed.bind(this);
+ this._onStepOutPressed = this._onStepOutPressed.bind(this);
+}
+
+ToolbarView.prototype = {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function() {
+ dumpn("Initializing the ToolbarView");
+
+ this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
+ this._resumeButton = document.getElementById("resume");
+ this._stepOverButton = document.getElementById("step-over");
+ this._stepInButton = document.getElementById("step-in");
+ this._stepOutButton = document.getElementById("step-out");
+ this._resumeOrderTooltip = new Tooltip(document);
+ this._resumeOrderTooltip.defaultPosition = TOOLBAR_ORDER_POPUP_POSITION;
+
+ let resumeKey = ShortcutUtils.prettifyShortcut(document.getElementById("resumeKey"));
+ let stepOverKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOverKey"));
+ let stepInKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepInKey"));
+ let stepOutKey = ShortcutUtils.prettifyShortcut(document.getElementById("stepOutKey"));
+ this._resumeTooltip = L10N.getFormatStr("resumeButtonTooltip", resumeKey);
+ this._pauseTooltip = L10N.getFormatStr("pauseButtonTooltip", resumeKey);
+ this._stepOverTooltip = L10N.getFormatStr("stepOverTooltip", stepOverKey);
+ this._stepInTooltip = L10N.getFormatStr("stepInTooltip", stepInKey);
+ this._stepOutTooltip = L10N.getFormatStr("stepOutTooltip", stepOutKey);
+
+ this._instrumentsPaneToggleButton.addEventListener("mousedown", this._onTogglePanesPressed, false);
+ this._resumeButton.addEventListener("mousedown", this._onResumePressed, false);
+ this._stepOverButton.addEventListener("mousedown", this._onStepOverPressed, false);
+ this._stepInButton.addEventListener("mousedown", this._onStepInPressed, false);
+ this._stepOutButton.addEventListener("mousedown", this._onStepOutPressed, false);
+
+ this._stepOverButton.setAttribute("tooltiptext", this._stepOverTooltip);
+ this._stepInButton.setAttribute("tooltiptext", this._stepInTooltip);
+ this._stepOutButton.setAttribute("tooltiptext", this._stepOutTooltip);
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function() {
+ dumpn("Destroying the ToolbarView");
+
+ this._instrumentsPaneToggleButton.removeEventListener("mousedown", this._onTogglePanesPressed, false);
+ this._resumeButton.removeEventListener("mousedown", this._onResumePressed, false);
+ this._stepOverButton.removeEventListener("mousedown", this._onStepOverPressed, false);
+ this._stepInButton.removeEventListener("mousedown", this._onStepInPressed, false);
+ this._stepOutButton.removeEventListener("mousedown", this._onStepOutPressed, false);
+ },
+
+ /**
+ * Display a warning when trying to resume a debuggee while another is paused.
+ * Debuggees must be unpaused in a Last-In-First-Out order.
+ *
+ * @param string aPausedUrl
+ * The URL of the last paused debuggee.
+ */
+ showResumeWarning: function(aPausedUrl) {
+ let label = L10N.getFormatStr("resumptionOrderPanelTitle", aPausedUrl);
+ let defaultStyle = "default-tooltip-simple-text-colors";
+ this._resumeOrderTooltip.setTextContent({ messages: [label], isAlertTooltip: true });
+ this._resumeOrderTooltip.show(this._resumeButton);
+ },
+
+ /**
+ * Sets the resume button state based on the debugger active thread.
+ *
+ * @param string aState
+ * Either "paused" or "attached".
+ */
+ toggleResumeButtonState: function(aState) {
+ // If we're paused, check and show a resume label on the button.
+ if (aState == "paused") {
+ this._resumeButton.setAttribute("checked", "true");
+ this._resumeButton.setAttribute("tooltiptext", this._resumeTooltip);
+ }
+ // If we're attached, do the opposite.
+ else if (aState == "attached") {
+ this._resumeButton.removeAttribute("checked");
+ this._resumeButton.setAttribute("tooltiptext", this._pauseTooltip);
+ }
+ },
+
+ /**
+ * Listener handling the toggle button click event.
+ */
+ _onTogglePanesPressed: function() {
+ DebuggerView.toggleInstrumentsPane({
+ visible: DebuggerView.instrumentsPaneHidden,
+ animated: true,
+ delayed: true
+ });
+ },
+
+ /**
+ * Listener handling the pause/resume button click event.
+ */
+ _onResumePressed: function() {
+ if (DebuggerController.StackFrames._currentFrameDescription != FRAME_TYPE.NORMAL) {
+ return;
+ }
+
+ if (DebuggerController.activeThread.paused) {
+ let warn = DebuggerController._ensureResumptionOrder;
+ DebuggerController.StackFrames.currentFrameDepth = -1;
+ DebuggerController.activeThread.resume(warn);
+ } else {
+ DebuggerController.ThreadState.interruptedByResumeButton = true;
+ DebuggerController.activeThread.interrupt();
+ }
+ },
+
+ /**
+ * Listener handling the step over button click event.
+ */
+ _onStepOverPressed: function() {
+ if (DebuggerController.activeThread.paused) {
+ DebuggerController.StackFrames.currentFrameDepth = -1;
+ let warn = DebuggerController._ensureResumptionOrder;
+ DebuggerController.activeThread.stepOver(warn);
+ }
+ },
+
+ /**
+ * Listener handling the step in button click event.
+ */
+ _onStepInPressed: function() {
+ if (DebuggerController.StackFrames._currentFrameDescription != FRAME_TYPE.NORMAL) {
+ return;
+ }
+
+ if (DebuggerController.activeThread.paused) {
+ DebuggerController.StackFrames.currentFrameDepth = -1;
+ let warn = DebuggerController._ensureResumptionOrder;
+ DebuggerController.activeThread.stepIn(warn);
+ }
+ },
+
+ /**
+ * Listener handling the step out button click event.
+ */
+ _onStepOutPressed: function() {
+ if (DebuggerController.activeThread.paused) {
+ DebuggerController.StackFrames.currentFrameDepth = -1;
+ let warn = DebuggerController._ensureResumptionOrder;
+ DebuggerController.activeThread.stepOut(warn);
+ }
+ },
+
+ _instrumentsPaneToggleButton: null,
+ _resumeButton: null,
+ _stepOverButton: null,
+ _stepInButton: null,
+ _stepOutButton: null,
+ _resumeOrderTooltip: null,
+ _resumeTooltip: "",
+ _pauseTooltip: "",
+ _stepOverTooltip: "",
+ _stepInTooltip: "",
+ _stepOutTooltip: ""
+};
+
+/**
+ * Functions handling the options UI.
+ */
+function OptionsView() {
+ dumpn("OptionsView was instantiated");
+
+ this._toggleAutoPrettyPrint = this._toggleAutoPrettyPrint.bind(this);
+ this._togglePauseOnExceptions = this._togglePauseOnExceptions.bind(this);
+ this._toggleIgnoreCaughtExceptions = this._toggleIgnoreCaughtExceptions.bind(this);
+ this._toggleShowPanesOnStartup = this._toggleShowPanesOnStartup.bind(this);
+ this._toggleShowVariablesOnlyEnum = this._toggleShowVariablesOnlyEnum.bind(this);
+ this._toggleShowVariablesFilterBox = this._toggleShowVariablesFilterBox.bind(this);
+ this._toggleShowOriginalSource = this._toggleShowOriginalSource.bind(this);
+ this._toggleAutoBlackBox = this._toggleAutoBlackBox.bind(this);
+}
+
+OptionsView.prototype = {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function() {
+ dumpn("Initializing the OptionsView");
+
+ this._button = document.getElementById("debugger-options");
+ this._autoPrettyPrint = document.getElementById("auto-pretty-print");
+ this._pauseOnExceptionsItem = document.getElementById("pause-on-exceptions");
+ this._ignoreCaughtExceptionsItem = document.getElementById("ignore-caught-exceptions");
+ this._showPanesOnStartupItem = document.getElementById("show-panes-on-startup");
+ this._showVariablesOnlyEnumItem = document.getElementById("show-vars-only-enum");
+ this._showVariablesFilterBoxItem = document.getElementById("show-vars-filter-box");
+ this._showOriginalSourceItem = document.getElementById("show-original-source");
+ this._autoBlackBoxItem = document.getElementById("auto-black-box");
+
+ this._autoPrettyPrint.setAttribute("checked", Prefs.autoPrettyPrint);
+ this._pauseOnExceptionsItem.setAttribute("checked", Prefs.pauseOnExceptions);
+ this._ignoreCaughtExceptionsItem.setAttribute("checked", Prefs.ignoreCaughtExceptions);
+ this._showPanesOnStartupItem.setAttribute("checked", Prefs.panesVisibleOnStartup);
+ this._showVariablesOnlyEnumItem.setAttribute("checked", Prefs.variablesOnlyEnumVisible);
+ this._showVariablesFilterBoxItem.setAttribute("checked", Prefs.variablesSearchboxVisible);
+ this._showOriginalSourceItem.setAttribute("checked", Prefs.sourceMapsEnabled);
+ this._autoBlackBoxItem.setAttribute("checked", Prefs.autoBlackBox);
+ },
+
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function() {
+ dumpn("Destroying the OptionsView");
+ // Nothing to do here yet.
+ },
+
+ /**
+ * Listener handling the 'gear menu' popup showing event.
+ */
+ _onPopupShowing: function() {
+ this._button.setAttribute("open", "true");
+ window.emit(EVENTS.OPTIONS_POPUP_SHOWING);
+ },
+
+ /**
+ * Listener handling the 'gear menu' popup hiding event.
+ */
+ _onPopupHiding: function() {
+ this._button.removeAttribute("open");
+ },
+
+ /**
+ * Listener handling the 'gear menu' popup hidden event.
+ */
+ _onPopupHidden: function() {
+ window.emit(EVENTS.OPTIONS_POPUP_HIDDEN);
+ },
+
+ /**
+ * Listener handling the 'auto pretty print' menuitem command.
+ */
+ _toggleAutoPrettyPrint: function(){
+ Prefs.autoPrettyPrint =
+ this._autoPrettyPrint.getAttribute("checked") == "true";
+ },
+
+ /**
+ * Listener handling the 'pause on exceptions' menuitem command.
+ */
+ _togglePauseOnExceptions: function() {
+ Prefs.pauseOnExceptions =
+ this._pauseOnExceptionsItem.getAttribute("checked") == "true";
+
+ DebuggerController.activeThread.pauseOnExceptions(
+ Prefs.pauseOnExceptions,
+ Prefs.ignoreCaughtExceptions);
+ },
+
+ _toggleIgnoreCaughtExceptions: function() {
+ Prefs.ignoreCaughtExceptions =
+ this._ignoreCaughtExceptionsItem.getAttribute("checked") == "true";
+
+ DebuggerController.activeThread.pauseOnExceptions(
+ Prefs.pauseOnExceptions,
+ Prefs.ignoreCaughtExceptions);
+ },
+
+ /**
+ * Listener handling the 'show panes on startup' menuitem command.
+ */
+ _toggleShowPanesOnStartup: function() {
+ Prefs.panesVisibleOnStartup =
+ this._showPanesOnStartupItem.getAttribute("checked") == "true";
+ },
+
+ /**
+ * Listener handling the 'show non-enumerables' menuitem command.
+ */
+ _toggleShowVariablesOnlyEnum: function() {
+ let pref = Prefs.variablesOnlyEnumVisible =
+ this._showVariablesOnlyEnumItem.getAttribute("checked") == "true";
+
+ DebuggerView.Variables.onlyEnumVisible = pref;
+ },
+
+ /**
+ * Listener handling the 'show variables searchbox' menuitem command.
+ */
+ _toggleShowVariablesFilterBox: function() {
+ let pref = Prefs.variablesSearchboxVisible =
+ this._showVariablesFilterBoxItem.getAttribute("checked") == "true";
+
+ DebuggerView.Variables.searchEnabled = pref;
+ },
+
+ /**
+ * Listener handling the 'show original source' menuitem command.
+ */
+ _toggleShowOriginalSource: function() {
+ let pref = Prefs.sourceMapsEnabled =
+ this._showOriginalSourceItem.getAttribute("checked") == "true";
+
+ // Don't block the UI while reconfiguring the server.
+ window.once(EVENTS.OPTIONS_POPUP_HIDDEN, () => {
+ // The popup panel needs more time to hide after triggering onpopuphidden.
+ window.setTimeout(() => {
+ DebuggerController.reconfigureThread({
+ useSourceMaps: pref,
+ autoBlackBox: Prefs.autoBlackBox
+ });
+ }, POPUP_HIDDEN_DELAY);
+ });
+ },
+
+ /**
+ * Listener handling the 'automatically black box minified sources' menuitem
+ * command.
+ */
+ _toggleAutoBlackBox: function() {
+ let pref = Prefs.autoBlackBox =
+ this._autoBlackBoxItem.getAttribute("checked") == "true";
+
+ // Don't block the UI while reconfiguring the server.
+ window.once(EVENTS.OPTIONS_POPUP_HIDDEN, () => {
+ // The popup panel needs more time to hide after triggering onpopuphidden.
+ window.setTimeout(() => {
+ DebuggerController.reconfigureThread({
+ useSourceMaps: Prefs.sourceMapsEnabled,
+ autoBlackBox: pref
+ });
+ }, POPUP_HIDDEN_DELAY);
+ });
+ },
+
+ _button: null,
+ _pauseOnExceptionsItem: null,
+ _showPanesOnStartupItem: null,
+ _showVariablesOnlyEnumItem: null,
+ _showVariablesFilterBoxItem: null,
+ _showOriginalSourceItem: null,
+ _autoBlackBoxItem: null
+};
+
+/**
+ * Functions handling the stackframes UI.
+ */
+function StackFramesView() {
+ dumpn("StackFramesView was instantiated");
+
+ this._onStackframeRemoved = this._onStackframeRemoved.bind(this);
+ this._onSelect = this._onSelect.bind(this);
+ this._onScroll = this._onScroll.bind(this);
+ this._afterScroll = this._afterScroll.bind(this);
+}
+
+StackFramesView.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function() {
+ dumpn("Initializing the StackFramesView");
+
+ this.widget = new BreadcrumbsWidget(document.getElementById("stackframes"));
+ this.widget.addEventListener("select", this._onSelect, false);
+ this.widget.addEventListener("scroll", this._onScroll, true);
+ window.addEventListener("resize", this._onScroll, true);
+
+ this.autoFocusOnFirstItem = false;
+ this.autoFocusOnSelection = false;
+
+ // This view's contents are also mirrored in a different container.
+ this._mirror = DebuggerView.StackFramesClassicList;
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function() {
+ dumpn("Destroying the StackFramesView");
+
+ this.widget.removeEventListener("select", this._onSelect, false);
+ this.widget.removeEventListener("scroll", this._onScroll, true);
+ window.removeEventListener("resize", this._onScroll, true);
+ },
+
+ /**
+ * Adds a frame in this stackframes container.
+ *
+ * @param string aTitle
+ * The frame title (function name).
+ * @param string aUrl
+ * The frame source url.
+ * @param string aLine
+ * The frame line number.
+ * @param number aDepth
+ * The frame depth in the stack.
+ * @param boolean aIsBlackBoxed
+ * Whether or not the frame is black boxed.
+ */
+ addFrame: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) {
+ // Blackboxed stack frames are collapsed into a single entry in
+ // the view. By convention, only the first frame is displayed.
+ if (aIsBlackBoxed) {
+ if (this._prevBlackBoxedUrl == aUrl) {
+ return;
+ }
+ this._prevBlackBoxedUrl = aUrl;
+ } else {
+ this._prevBlackBoxedUrl = null;
+ }
+
+ // Create the element node for the stack frame item.
+ let frameView = this._createFrameView.apply(this, arguments);
+
+ // Append a stack frame item to this container.
+ this.push([frameView], {
+ index: 0, /* specifies on which position should the item be appended */
+ attachment: {
+ title: aTitle,
+ url: aUrl,
+ line: aLine,
+ depth: aDepth
+ },
+ // Make sure that when the stack frame item is removed, the corresponding
+ // mirrored item in the classic list is also removed.
+ finalize: this._onStackframeRemoved
+ });
+
+ // Mirror this newly inserted item inside the "Call Stack" tab.
+ this._mirror.addFrame(aTitle, aUrl, aLine, aDepth);
+ },
+
+ /**
+ * Selects the frame at the specified depth in this container.
+ * @param number aDepth
+ */
+ set selectedDepth(aDepth) {
+ this.selectedItem = aItem => aItem.attachment.depth == aDepth;
+ },
+
+ /**
+ * Gets the currently selected stack frame's depth in this container.
+ * This will essentially be the opposite of |selectedIndex|, which deals
+ * with the position in the view, where the last item added is actually
+ * the bottommost, not topmost.
+ * @return number
+ */
+ get selectedDepth() {
+ return this.selectedItem.attachment.depth;
+ },
+
+ /**
+ * Specifies if the active thread has more frames that need to be loaded.
+ */
+ dirty: false,
+
+ /**
+ * Customization function for creating an item's UI.
+ *
+ * @param string aTitle
+ * The frame title to be displayed in the list.
+ * @param string aUrl
+ * The frame source url.
+ * @param string aLine
+ * The frame line number.
+ * @param number aDepth
+ * The frame depth in the stack.
+ * @param boolean aIsBlackBoxed
+ * Whether or not the frame is black boxed.
+ * @return nsIDOMNode
+ * The stack frame view.
+ */
+ _createFrameView: function(aTitle, aUrl, aLine, aDepth, aIsBlackBoxed) {
+ let container = document.createElement("hbox");
+ container.id = "stackframe-" + aDepth;
+ container.className = "dbg-stackframe";
+
+ let frameDetails = SourceUtils.trimUrlLength(
+ SourceUtils.getSourceLabel(aUrl),
+ STACK_FRAMES_SOURCE_URL_MAX_LENGTH,
+ STACK_FRAMES_SOURCE_URL_TRIM_SECTION);
+
+ if (aIsBlackBoxed) {
+ container.classList.add("dbg-stackframe-black-boxed");
+ } else {
+ let frameTitleNode = document.createElement("label");
+ frameTitleNode.className = "plain dbg-stackframe-title breadcrumbs-widget-item-tag";
+ frameTitleNode.setAttribute("value", aTitle);
+ container.appendChild(frameTitleNode);
+
+ frameDetails += SEARCH_LINE_FLAG + aLine;
+ }
+
+ let frameDetailsNode = document.createElement("label");
+ frameDetailsNode.className = "plain dbg-stackframe-details breadcrumbs-widget-item-id";
+ frameDetailsNode.setAttribute("value", frameDetails);
+ container.appendChild(frameDetailsNode);
+
+ return container;
+ },
+
+ /**
+ * Function called each time a stack frame item is removed.
+ *
+ * @param object aItem
+ * The corresponding item.
+ */
+ _onStackframeRemoved: function(aItem) {
+ dumpn("Finalizing stackframe item: " + aItem.stringify());
+
+ // Remove the mirrored item in the classic list.
+ let depth = aItem.attachment.depth;
+ this._mirror.remove(this._mirror.getItemForAttachment(e => e.depth == depth));
+
+ // Forget the previously blackboxed stack frame url.
+ this._prevBlackBoxedUrl = null;
+ },
+
+ /**
+ * The select listener for the stackframes container.
+ */
+ _onSelect: function(e) {
+ let stackframeItem = this.selectedItem;
+ if (stackframeItem) {
+ // The container is not empty and an actual item was selected.
+ let depth = stackframeItem.attachment.depth;
+
+ // Mirror the selected item in the classic list.
+ this.suppressSelectionEvents = true;
+ this._mirror.selectedItem = e => e.attachment.depth == depth;
+ this.suppressSelectionEvents = false;
+
+ DebuggerController.StackFrames.selectFrame(depth);
+ }
+ },
+
+ /**
+ * The scroll listener for the stackframes container.
+ */
+ _onScroll: function() {
+ // Update the stackframes container only if we have to.
+ if (!this.dirty) {
+ return;
+ }
+ // Allow requests to settle down first.
+ setNamedTimeout("stack-scroll", STACK_FRAMES_SCROLL_DELAY, this._afterScroll);
+ },
+
+ /**
+ * Requests the addition of more frames from the controller.
+ */
+ _afterScroll: function() {
+ let scrollPosition = this.widget.getAttribute("scrollPosition");
+ let scrollWidth = this.widget.getAttribute("scrollWidth");
+
+ // If the stackframes container scrolled almost to the end, with only
+ // 1/10 of a breadcrumb remaining, load more content.
+ if (scrollPosition - scrollWidth / 10 < 1) {
+ this.ensureIndexIsVisible(CALL_STACK_PAGE_SIZE - 1);
+ this.dirty = false;
+
+ // Loads more stack frames from the debugger server cache.
+ DebuggerController.StackFrames.addMoreFrames();
+ }
+ },
+
+ _mirror: null,
+ _prevBlackBoxedUrl: null
+});
+
+/*
+ * Functions handling the stackframes classic list UI.
+ * Controlled by the DebuggerView.StackFrames isntance.
+ */
+function StackFramesClassicListView() {
+ dumpn("StackFramesClassicListView was instantiated");
+
+ this._onSelect = this._onSelect.bind(this);
+}
+
+StackFramesClassicListView.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function() {
+ dumpn("Initializing the StackFramesClassicListView");
+
+ this.widget = new SideMenuWidget(document.getElementById("callstack-list"));
+ this.widget.addEventListener("select", this._onSelect, false);
+
+ this.emptyText = L10N.getStr("noStackFramesText");
+ this.autoFocusOnFirstItem = false;
+ this.autoFocusOnSelection = false;
+
+ // This view's contents are also mirrored in a different container.
+ this._mirror = DebuggerView.StackFrames;
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function() {
+ dumpn("Destroying the StackFramesClassicListView");
+
+ this.widget.removeEventListener("select", this._onSelect, false);
+ },
+
+ /**
+ * Adds a frame in this stackframes container.
+ *
+ * @param string aTitle
+ * The frame title (function name).
+ * @param string aUrl
+ * The frame source url.
+ * @param string aLine
+ * The frame line number.
+ * @param number aDepth
+ * The frame depth in the stack.
+ */
+ addFrame: function(aTitle, aUrl, aLine, aDepth) {
+ // Create the element node for the stack frame item.
+ let frameView = this._createFrameView.apply(this, arguments);
+
+ // Append a stack frame item to this container.
+ this.push([frameView], {
+ attachment: {
+ depth: aDepth
+ }
+ });
+ },
+
+ /**
+ * Customization function for creating an item's UI.
+ *
+ * @param string aTitle
+ * The frame title to be displayed in the list.
+ * @param string aUrl
+ * The frame source url.
+ * @param string aLine
+ * The frame line number.
+ * @param number aDepth
+ * The frame depth in the stack.
+ * @return nsIDOMNode
+ * The stack frame view.
+ */
+ _createFrameView: function(aTitle, aUrl, aLine, aDepth) {
+ let container = document.createElement("hbox");
+ container.id = "classic-stackframe-" + aDepth;
+ container.className = "dbg-classic-stackframe";
+ container.setAttribute("flex", "1");
+
+ let frameTitleNode = document.createElement("label");
+ frameTitleNode.className = "plain dbg-classic-stackframe-title";
+ frameTitleNode.setAttribute("value", aTitle);
+ frameTitleNode.setAttribute("crop", "center");
+
+ let frameDetailsNode = document.createElement("hbox");
+ frameDetailsNode.className = "plain dbg-classic-stackframe-details";
+
+ let frameUrlNode = document.createElement("label");
+ frameUrlNode.className = "plain dbg-classic-stackframe-details-url";
+ frameUrlNode.setAttribute("value", SourceUtils.getSourceLabel(aUrl));
+ frameUrlNode.setAttribute("crop", "center");
+ frameDetailsNode.appendChild(frameUrlNode);
+
+ let frameDetailsSeparator = document.createElement("label");
+ frameDetailsSeparator.className = "plain dbg-classic-stackframe-details-sep";
+ frameDetailsSeparator.setAttribute("value", SEARCH_LINE_FLAG);
+ frameDetailsNode.appendChild(frameDetailsSeparator);
+
+ let frameLineNode = document.createElement("label");
+ frameLineNode.className = "plain dbg-classic-stackframe-details-line";
+ frameLineNode.setAttribute("value", aLine);
+ frameDetailsNode.appendChild(frameLineNode);
+
+ container.appendChild(frameTitleNode);
+ container.appendChild(frameDetailsNode);
+
+ return container;
+ },
+
+ /**
+ * The select listener for the stackframes container.
+ */
+ _onSelect: function(e) {
+ let stackframeItem = this.selectedItem;
+ if (stackframeItem) {
+ // The container is not empty and an actual item was selected.
+ // Mirror the selected item in the breadcrumbs list.
+ let depth = stackframeItem.attachment.depth;
+ this._mirror.selectedItem = e => e.attachment.depth == depth;
+ }
+ },
+
+ _mirror: null
+});
+
+/**
+ * Functions handling the filtering UI.
+ */
+function FilterView() {
+ dumpn("FilterView was instantiated");
+
+ this._onClick = this._onClick.bind(this);
+ this._onInput = this._onInput.bind(this);
+ this._onKeyPress = this._onKeyPress.bind(this);
+ this._onBlur = this._onBlur.bind(this);
+}
+
+FilterView.prototype = {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function() {
+ dumpn("Initializing the FilterView");
+
+ this._searchbox = document.getElementById("searchbox");
+ this._searchboxHelpPanel = document.getElementById("searchbox-help-panel");
+ this._filterLabel = document.getElementById("filter-label");
+ this._globalOperatorButton = document.getElementById("global-operator-button");
+ this._globalOperatorLabel = document.getElementById("global-operator-label");
+ this._functionOperatorButton = document.getElementById("function-operator-button");
+ this._functionOperatorLabel = document.getElementById("function-operator-label");
+ this._tokenOperatorButton = document.getElementById("token-operator-button");
+ this._tokenOperatorLabel = document.getElementById("token-operator-label");
+ this._lineOperatorButton = document.getElementById("line-operator-button");
+ this._lineOperatorLabel = document.getElementById("line-operator-label");
+ this._variableOperatorButton = document.getElementById("variable-operator-button");
+ this._variableOperatorLabel = document.getElementById("variable-operator-label");
+
+ this._fileSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("fileSearchKey"));
+ this._globalSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("globalSearchKey"));
+ this._filteredFunctionsKey = ShortcutUtils.prettifyShortcut(document.getElementById("functionSearchKey"));
+ this._tokenSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("tokenSearchKey"));
+ this._lineSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("lineSearchKey"));
+ this._variableSearchKey = ShortcutUtils.prettifyShortcut(document.getElementById("variableSearchKey"));
+
+ this._searchbox.addEventListener("click", this._onClick, false);
+ this._searchbox.addEventListener("select", this._onInput, false);
+ this._searchbox.addEventListener("input", this._onInput, false);
+ this._searchbox.addEventListener("keypress", this._onKeyPress, false);
+ this._searchbox.addEventListener("blur", this._onBlur, false);
+
+ let placeholder = L10N.getFormatStr("emptySearchText", this._fileSearchKey);
+ this._searchbox.setAttribute("placeholder", placeholder);
+
+ this._globalOperatorButton.setAttribute("label", SEARCH_GLOBAL_FLAG);
+ this._functionOperatorButton.setAttribute("label", SEARCH_FUNCTION_FLAG);
+ this._tokenOperatorButton.setAttribute("label", SEARCH_TOKEN_FLAG);
+ this._lineOperatorButton.setAttribute("label", SEARCH_LINE_FLAG);
+ this._variableOperatorButton.setAttribute("label", SEARCH_VARIABLE_FLAG);
+
+ this._filterLabel.setAttribute("value",
+ L10N.getFormatStr("searchPanelFilter", this._fileSearchKey));
+ this._globalOperatorLabel.setAttribute("value",
+ L10N.getFormatStr("searchPanelGlobal", this._globalSearchKey));
+ this._functionOperatorLabel.setAttribute("value",
+ L10N.getFormatStr("searchPanelFunction", this._filteredFunctionsKey));
+ this._tokenOperatorLabel.setAttribute("value",
+ L10N.getFormatStr("searchPanelToken", this._tokenSearchKey));
+ this._lineOperatorLabel.setAttribute("value",
+ L10N.getFormatStr("searchPanelGoToLine", this._lineSearchKey));
+ this._variableOperatorLabel.setAttribute("value",
+ L10N.getFormatStr("searchPanelVariable", this._variableSearchKey));
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function() {
+ dumpn("Destroying the FilterView");
+
+ this._searchbox.removeEventListener("click", this._onClick, false);
+ this._searchbox.removeEventListener("select", this._onInput, false);
+ this._searchbox.removeEventListener("input", this._onInput, false);
+ this._searchbox.removeEventListener("keypress", this._onKeyPress, false);
+ this._searchbox.removeEventListener("blur", this._onBlur, false);
+ },
+
+ /**
+ * Gets the entered operator and arguments in the searchbox.
+ * @return array
+ */
+ get searchData() {
+ let operator = "", args = [];
+
+ let rawValue = this._searchbox.value;
+ let rawLength = rawValue.length;
+ let globalFlagIndex = rawValue.indexOf(SEARCH_GLOBAL_FLAG);
+ let functionFlagIndex = rawValue.indexOf(SEARCH_FUNCTION_FLAG);
+ let variableFlagIndex = rawValue.indexOf(SEARCH_VARIABLE_FLAG);
+ let tokenFlagIndex = rawValue.lastIndexOf(SEARCH_TOKEN_FLAG);
+ let lineFlagIndex = rawValue.lastIndexOf(SEARCH_LINE_FLAG);
+
+ // This is not a global, function or variable search, allow file/line flags.
+ if (globalFlagIndex != 0 && functionFlagIndex != 0 && variableFlagIndex != 0) {
+ // Token search has precedence over line search.
+ if (tokenFlagIndex != -1) {
+ operator = SEARCH_TOKEN_FLAG;
+ args.push(rawValue.slice(0, tokenFlagIndex)); // file
+ args.push(rawValue.substr(tokenFlagIndex + 1, rawLength)); // token
+ } else if (lineFlagIndex != -1) {
+ operator = SEARCH_LINE_FLAG;
+ args.push(rawValue.slice(0, lineFlagIndex)); // file
+ args.push(+rawValue.substr(lineFlagIndex + 1, rawLength) || 0); // line
+ } else {
+ args.push(rawValue);
+ }
+ }
+ // Global searches dissalow the use of file or line flags.
+ else if (globalFlagIndex == 0) {
+ operator = SEARCH_GLOBAL_FLAG;
+ args.push(rawValue.slice(1));
+ }
+ // Function searches dissalow the use of file or line flags.
+ else if (functionFlagIndex == 0) {
+ operator = SEARCH_FUNCTION_FLAG;
+ args.push(rawValue.slice(1));
+ }
+ // Variable searches dissalow the use of file or line flags.
+ else if (variableFlagIndex == 0) {
+ operator = SEARCH_VARIABLE_FLAG;
+ args.push(rawValue.slice(1));
+ }
+
+ return [operator, args];
+ },
+
+ /**
+ * Returns the current search operator.
+ * @return string
+ */
+ get searchOperator() this.searchData[0],
+
+ /**
+ * Returns the current search arguments.
+ * @return array
+ */
+ get searchArguments() this.searchData[1],
+
+ /**
+ * Clears the text from the searchbox and any changed views.
+ */
+ clearSearch: function() {
+ this._searchbox.value = "";
+ this.clearViews();
+ },
+
+ /**
+ * Clears all the views that may pop up when searching.
+ */
+ clearViews: function() {
+ DebuggerView.GlobalSearch.clearView();
+ DebuggerView.FilteredSources.clearView();
+ DebuggerView.FilteredFunctions.clearView();
+ this._searchboxHelpPanel.hidePopup();
+ },
+
+ /**
+ * Performs a line search if necessary.
+ * (Jump to lines in the currently visible source).
+ *
+ * @param number aLine
+ * The source line number to jump to.
+ */
+ _performLineSearch: function(aLine) {
+ // Make sure we're actually searching for a valid line.
+ if (aLine) {
+ DebuggerView.editor.setCursor({ line: aLine - 1, ch: 0 }, "center");
+ }
+ },
+
+ /**
+ * Performs a token search if necessary.
+ * (Search for tokens in the currently visible source).
+ *
+ * @param string aToken
+ * The source token to find.
+ */
+ _performTokenSearch: function(aToken) {
+ // Make sure we're actually searching for a valid token.
+ if (!aToken) {
+ return;
+ }
+ DebuggerView.editor.find(aToken);
+ },
+
+ /**
+ * The click listener for the search container.
+ */
+ _onClick: function() {
+ // If there's some text in the searchbox, displaying a panel would
+ // interfere with double/triple click default behaviors.
+ if (!this._searchbox.value) {
+ this._searchboxHelpPanel.openPopup(this._searchbox);
+ }
+ },
+
+ /**
+ * The input listener for the search container.
+ */
+ _onInput: function() {
+ this.clearViews();
+
+ // Make sure we're actually searching for something.
+ if (!this._searchbox.value) {
+ return;
+ }
+
+ // Perform the required search based on the specified operator.
+ switch (this.searchOperator) {
+ case SEARCH_GLOBAL_FLAG:
+ // Schedule a global search for when the user stops typing.
+ DebuggerView.GlobalSearch.scheduleSearch(this.searchArguments[0]);
+ break;
+ case SEARCH_FUNCTION_FLAG:
+ // Schedule a function search for when the user stops typing.
+ DebuggerView.FilteredFunctions.scheduleSearch(this.searchArguments[0]);
+ break;
+ case SEARCH_VARIABLE_FLAG:
+ // Schedule a variable search for when the user stops typing.
+ DebuggerView.Variables.scheduleSearch(this.searchArguments[0]);
+ break;
+ case SEARCH_TOKEN_FLAG:
+ // Schedule a file+token search for when the user stops typing.
+ DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]);
+ this._performTokenSearch(this.searchArguments[1]);
+ break;
+ case SEARCH_LINE_FLAG:
+ // Schedule a file+line search for when the user stops typing.
+ DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]);
+ this._performLineSearch(this.searchArguments[1]);
+ break;
+ default:
+ // Schedule a file only search for when the user stops typing.
+ DebuggerView.FilteredSources.scheduleSearch(this.searchArguments[0]);
+ break;
+ }
+ },
+
+ /**
+ * The key press listener for the search container.
+ */
+ _onKeyPress: function(e) {
+ // This attribute is not implemented in Gecko at this time, see bug 680830.
+ e.char = String.fromCharCode(e.charCode);
+
+ // Perform the required action based on the specified operator.
+ let [operator, args] = this.searchData;
+ let isGlobalSearch = operator == SEARCH_GLOBAL_FLAG;
+ let isFunctionSearch = operator == SEARCH_FUNCTION_FLAG;
+ let isVariableSearch = operator == SEARCH_VARIABLE_FLAG;
+ let isTokenSearch = operator == SEARCH_TOKEN_FLAG;
+ let isLineSearch = operator == SEARCH_LINE_FLAG;
+ let isFileOnlySearch = !operator && args.length == 1;
+
+ // Depending on the pressed keys, determine to correct action to perform.
+ let actionToPerform;
+
+ // Meta+G and Ctrl+N focus next matches.
+ if ((e.char == "g" && e.metaKey) || e.char == "n" && e.ctrlKey) {
+ actionToPerform = "selectNext";
+ }
+ // Meta+Shift+G and Ctrl+P focus previous matches.
+ else if ((e.char == "G" && e.metaKey) || e.char == "p" && e.ctrlKey) {
+ actionToPerform = "selectPrev";
+ }
+ // Return, enter, down and up keys focus next or previous matches, while
+ // the escape key switches focus from the search container.
+ else switch (e.keyCode) {
+ case e.DOM_VK_RETURN:
+ var isReturnKey = true;
+ // If the shift key is pressed, focus on the previous result
+ actionToPerform = e.shiftKey ? "selectPrev" : "selectNext";
+ break;
+ case e.DOM_VK_DOWN:
+ actionToPerform = "selectNext";
+ break;
+ case e.DOM_VK_UP:
+ actionToPerform = "selectPrev";
+ break;
+ }
+
+ // If there's no action to perform, or no operator, file line or token
+ // were specified, then this is either a broken or empty search.
+ if (!actionToPerform || (!operator && !args.length)) {
+ DebuggerView.editor.dropSelection();
+ return;
+ }
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ // Jump to the next/previous entry in the global search, or perform
+ // a new global search immediately
+ if (isGlobalSearch) {
+ let targetView = DebuggerView.GlobalSearch;
+ if (!isReturnKey) {
+ targetView[actionToPerform]();
+ } else if (targetView.hidden) {
+ targetView.scheduleSearch(args[0], 0);
+ }
+ return;
+ }
+
+ // Jump to the next/previous entry in the function search, perform
+ // a new function search immediately, or clear it.
+ if (isFunctionSearch) {
+ let targetView = DebuggerView.FilteredFunctions;
+ if (!isReturnKey) {
+ targetView[actionToPerform]();
+ } else if (targetView.hidden) {
+ targetView.scheduleSearch(args[0], 0);
+ } else {
+ if (!targetView.selectedItem) {
+ targetView.selectedIndex = 0;
+ }
+ this.clearSearch();
+ }
+ return;
+ }
+
+ // Perform a new variable search immediately.
+ if (isVariableSearch) {
+ let targetView = DebuggerView.Variables;
+ if (isReturnKey) {
+ targetView.scheduleSearch(args[0], 0);
+ }
+ return;
+ }
+
+ // Jump to the next/previous entry in the file search, perform
+ // a new file search immediately, or clear it.
+ if (isFileOnlySearch) {
+ let targetView = DebuggerView.FilteredSources;
+ if (!isReturnKey) {
+ targetView[actionToPerform]();
+ } else if (targetView.hidden) {
+ targetView.scheduleSearch(args[0], 0);
+ } else {
+ if (!targetView.selectedItem) {
+ targetView.selectedIndex = 0;
+ }
+ this.clearSearch();
+ }
+ return;
+ }
+
+ // Jump to the next/previous instance of the currently searched token.
+ if (isTokenSearch) {
+ let methods = { selectNext: "findNext", selectPrev: "findPrev" };
+ DebuggerView.editor[methods[actionToPerform]]();
+ return;
+ }
+
+ // Increment/decrement the currently searched caret line.
+ if (isLineSearch) {
+ let [, line] = args;
+ let amounts = { selectNext: 1, selectPrev: -1 };
+
+ // Modify the line number and jump to it.
+ line += !isReturnKey ? amounts[actionToPerform] : 0;
+ let lineCount = DebuggerView.editor.lineCount();
+ let lineTarget = line < 1 ? 1 : line > lineCount ? lineCount : line;
+ this._doSearch(SEARCH_LINE_FLAG, lineTarget);
+ return;
+ }
+ },
+
+ /**
+ * The blur listener for the search container.
+ */
+ _onBlur: function() {
+ this.clearViews();
+ },
+
+ /**
+ * Called when a filtering key sequence was pressed.
+ *
+ * @param string aOperator
+ * The operator to use for filtering.
+ */
+ _doSearch: function(aOperator = "", aText = "") {
+ this._searchbox.focus();
+ this._searchbox.value = ""; // Need to clear value beforehand. Bug 779738.
+
+ if (aText) {
+ this._searchbox.value = aOperator + aText;
+ return;
+ }
+ if (DebuggerView.editor.somethingSelected()) {
+ this._searchbox.value = aOperator + DebuggerView.editor.getSelection();
+ return;
+ }
+ if (SEARCH_AUTOFILL.indexOf(aOperator) != -1) {
+ let cursor = DebuggerView.editor.getCursor();
+ let content = DebuggerView.editor.getText();
+ let location = DebuggerView.Sources.selectedItem.attachment.source.url;
+ let source = DebuggerController.Parser.get(content, location);
+ let identifier = source.getIdentifierAt({ line: cursor.line+1, column: cursor.ch });
+
+ if (identifier && identifier.name) {
+ this._searchbox.value = aOperator + identifier.name;
+ this._searchbox.select();
+ this._searchbox.selectionStart += aOperator.length;
+ return;
+ }
+ }
+ this._searchbox.value = aOperator;
+ },
+
+ /**
+ * Called when the source location filter key sequence was pressed.
+ */
+ _doFileSearch: function() {
+ this._doSearch();
+ this._searchboxHelpPanel.openPopup(this._searchbox);
+ },
+
+ /**
+ * Called when the global search filter key sequence was pressed.
+ */
+ _doGlobalSearch: function() {
+ this._doSearch(SEARCH_GLOBAL_FLAG);
+ this._searchboxHelpPanel.hidePopup();
+ },
+
+ /**
+ * Called when the source function filter key sequence was pressed.
+ */
+ _doFunctionSearch: function() {
+ this._doSearch(SEARCH_FUNCTION_FLAG);
+ this._searchboxHelpPanel.hidePopup();
+ },
+
+ /**
+ * Called when the source token filter key sequence was pressed.
+ */
+ _doTokenSearch: function() {
+ this._doSearch(SEARCH_TOKEN_FLAG);
+ this._searchboxHelpPanel.hidePopup();
+ },
+
+ /**
+ * Called when the source line filter key sequence was pressed.
+ */
+ _doLineSearch: function() {
+ this._doSearch(SEARCH_LINE_FLAG);
+ this._searchboxHelpPanel.hidePopup();
+ },
+
+ /**
+ * Called when the variable search filter key sequence was pressed.
+ */
+ _doVariableSearch: function() {
+ this._doSearch(SEARCH_VARIABLE_FLAG);
+ this._searchboxHelpPanel.hidePopup();
+ },
+
+ /**
+ * Called when the variables focus key sequence was pressed.
+ */
+ _doVariablesFocus: function() {
+ DebuggerView.showInstrumentsPane();
+ DebuggerView.Variables.focusFirstVisibleItem();
+ },
+
+ _searchbox: null,
+ _searchboxHelpPanel: null,
+ _globalOperatorButton: null,
+ _globalOperatorLabel: null,
+ _functionOperatorButton: null,
+ _functionOperatorLabel: null,
+ _tokenOperatorButton: null,
+ _tokenOperatorLabel: null,
+ _lineOperatorButton: null,
+ _lineOperatorLabel: null,
+ _variableOperatorButton: null,
+ _variableOperatorLabel: null,
+ _fileSearchKey: "",
+ _globalSearchKey: "",
+ _filteredFunctionsKey: "",
+ _tokenSearchKey: "",
+ _lineSearchKey: "",
+ _variableSearchKey: "",
+};
+
+/**
+ * Functions handling the filtered sources UI.
+ */
+function FilteredSourcesView() {
+ dumpn("FilteredSourcesView was instantiated");
+
+ this._onClick = this._onClick.bind(this);
+ this._onSelect = this._onSelect.bind(this);
+}
+
+FilteredSourcesView.prototype = Heritage.extend(ResultsPanelContainer.prototype, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function() {
+ dumpn("Initializing the FilteredSourcesView");
+
+ this.anchor = document.getElementById("searchbox");
+ this.widget.addEventListener("select", this._onSelect, false);
+ this.widget.addEventListener("click", this._onClick, false);
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function() {
+ dumpn("Destroying the FilteredSourcesView");
+
+ this.widget.removeEventListener("select", this._onSelect, false);
+ this.widget.removeEventListener("click", this._onClick, false);
+ this.anchor = null;
+ },
+
+ /**
+ * Schedules searching for a source.
+ *
+ * @param string aToken
+ * The function to search for.
+ * @param number aWait
+ * The amount of milliseconds to wait until draining.
+ */
+ scheduleSearch: function(aToken, aWait) {
+ // The amount of time to wait for the requests to settle.
+ let maxDelay = FILE_SEARCH_ACTION_MAX_DELAY;
+ let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
+
+ // Allow requests to settle down first.
+ setNamedTimeout("sources-search", delay, () => this._doSearch(aToken));
+ },
+
+ /**
+ * Finds file matches in all the displayed sources.
+ *
+ * @param string aToken
+ * The string to search for.
+ */
+ _doSearch: function(aToken, aStore = []) {
+ // Don't continue filtering if the searched token is an empty string.
+ // In contrast with function searching, in this case we don't want to
+ // show a list of all the files when no search token was supplied.
+ if (!aToken) {
+ return;
+ }
+
+ for (let item of DebuggerView.Sources.items) {
+ let lowerCaseLabel = item.attachment.label.toLowerCase();
+ let lowerCaseToken = aToken.toLowerCase();
+ if (lowerCaseLabel.match(lowerCaseToken)) {
+ aStore.push(item);
+ }
+
+ // Once the maximum allowed number of results is reached, proceed
+ // with building the UI immediately.
+ if (aStore.length >= RESULTS_PANEL_MAX_RESULTS) {
+ this._syncView(aStore);
+ return;
+ }
+ }
+
+ // Couldn't reach the maximum allowed number of results, but that's ok,
+ // continue building the UI.
+ this._syncView(aStore);
+ },
+
+ /**
+ * Updates the list of sources displayed in this container.
+ *
+ * @param array aSearchResults
+ * The results array, containing search details for each source.
+ */
+ _syncView: function(aSearchResults) {
+ // If there are no matches found, keep the popup hidden and avoid
+ // creating the view.
+ if (!aSearchResults.length) {
+ window.emit(EVENTS.FILE_SEARCH_MATCH_NOT_FOUND);
+ return;
+ }
+
+ for (let item of aSearchResults) {
+ let url = item.attachment.source.url;
+
+ if (url) {
+ // Create the element node for the location item.
+ let itemView = this._createItemView(
+ SourceUtils.trimUrlLength(item.attachment.label),
+ SourceUtils.trimUrlLength(url, 0, "start")
+ );
+
+ // Append a location item to this container for each match.
+ this.push([itemView], {
+ index: -1, /* specifies on which position should the item be appended */
+ attachment: {
+ url: url
+ }
+ });
+ }
+ }
+
+ // There's at least one item displayed in this container. Don't select it
+ // automatically if not forced (by tests) or in tandem with an operator.
+ if (this._autoSelectFirstItem || DebuggerView.Filtering.searchOperator) {
+ this.selectedIndex = 0;
+ }
+ this.hidden = false;
+
+ // Signal that file search matches were found and displayed.
+ window.emit(EVENTS.FILE_SEARCH_MATCH_FOUND);
+ },
+
+ /**
+ * The click listener for this container.
+ */
+ _onClick: function(e) {
+ let locationItem = this.getItemForElement(e.target);
+ if (locationItem) {
+ this.selectedItem = locationItem;
+ DebuggerView.Filtering.clearSearch();
+ }
+ },
+
+ /**
+ * The select listener for this container.
+ *
+ * @param object aItem
+ * The item associated with the element to select.
+ */
+ _onSelect: function({ detail: locationItem }) {
+ if (locationItem) {
+ let actor = DebuggerView.Sources.getActorForLocation({ url: locationItem.attachment.url });
+ DebuggerView.setEditorLocation(actor, undefined, {
+ noCaret: true,
+ noDebug: true
+ });
+ }
+ }
+});
+
+/**
+ * Functions handling the function search UI.
+ */
+function FilteredFunctionsView() {
+ dumpn("FilteredFunctionsView was instantiated");
+
+ this._onClick = this._onClick.bind(this);
+ this._onSelect = this._onSelect.bind(this);
+}
+
+FilteredFunctionsView.prototype = Heritage.extend(ResultsPanelContainer.prototype, {
+ /**
+ * Initialization function, called when the debugger is started.
+ */
+ initialize: function() {
+ dumpn("Initializing the FilteredFunctionsView");
+
+ this.anchor = document.getElementById("searchbox");
+ this.widget.addEventListener("select", this._onSelect, false);
+ this.widget.addEventListener("click", this._onClick, false);
+ },
+
+ /**
+ * Destruction function, called when the debugger is closed.
+ */
+ destroy: function() {
+ dumpn("Destroying the FilteredFunctionsView");
+
+ this.widget.removeEventListener("select", this._onSelect, false);
+ this.widget.removeEventListener("click", this._onClick, false);
+ this.anchor = null;
+ },
+
+ /**
+ * Schedules searching for a function in all of the sources.
+ *
+ * @param string aToken
+ * The function to search for.
+ * @param number aWait
+ * The amount of milliseconds to wait until draining.
+ */
+ scheduleSearch: function(aToken, aWait) {
+ // The amount of time to wait for the requests to settle.
+ let maxDelay = FUNCTION_SEARCH_ACTION_MAX_DELAY;
+ let delay = aWait === undefined ? maxDelay / aToken.length : aWait;
+
+ // Allow requests to settle down first.
+ setNamedTimeout("function-search", delay, () => {
+ // Start fetching as many sources as possible, then perform the search.
+ let actors = DebuggerView.Sources.values;
+ let sourcesFetched = DebuggerController.SourceScripts.getTextForSources(actors);
+ sourcesFetched.then(aSources => this._doSearch(aToken, aSources));
+ });
+ },
+
+ /**
+ * Finds function matches in all the sources stored in the cache, and groups
+ * them by location and line number.
+ *
+ * @param string aToken
+ * The string to search for.
+ * @param array aSources
+ * An array of [url, text] tuples for each source.
+ */
+ _doSearch: function(aToken, aSources, aStore = []) {
+ // Continue parsing even if the searched token is an empty string, to
+ // cache the syntax tree nodes generated by the reflection API.
+
+ // Make sure the currently displayed source is parsed first. Once the
+ // maximum allowed number of results are found, parsing will be halted.
+ let currentActor = DebuggerView.Sources.selectedValue;
+ let currentSource = aSources.filter(([actor]) => actor == currentActor)[0];
+ aSources.splice(aSources.indexOf(currentSource), 1);
+ aSources.unshift(currentSource);
+
+ // If not searching for a specific function, only parse the displayed source,
+ // which is now the first item in the sources array.
+ if (!aToken) {
+ aSources.splice(1);
+ }
+
+ for (let [actor, contents] of aSources) {
+ let item = DebuggerView.Sources.getItemByValue(actor);
+ let url = item.attachment.source.url;
+ if (!url) {
+ continue;
+ }
+
+ let parsedSource = DebuggerController.Parser.get(contents, url);
+ let sourceResults = parsedSource.getNamedFunctionDefinitions(aToken);
+
+ for (let scriptResult of sourceResults) {
+ for (let parseResult of scriptResult) {
+ aStore.push({
+ sourceUrl: scriptResult.sourceUrl,
+ scriptOffset: scriptResult.scriptOffset,
+ functionName: parseResult.functionName,
+ functionLocation: parseResult.functionLocation,
+ inferredName: parseResult.inferredName,
+ inferredChain: parseResult.inferredChain,
+ inferredLocation: parseResult.inferredLocation
+ });
+
+ // Once the maximum allowed number of results is reached, proceed
+ // with building the UI immediately.
+ if (aStore.length >= RESULTS_PANEL_MAX_RESULTS) {
+ this._syncView(aStore);
+ return;
+ }
+ }
+ }
+ }
+
+ // Couldn't reach the maximum allowed number of results, but that's ok,
+ // continue building the UI.
+ this._syncView(aStore);
+ },
+
+ /**
+ * Updates the list of functions displayed in this container.
+ *
+ * @param array aSearchResults
+ * The results array, containing search details for each source.
+ */
+ _syncView: function(aSearchResults) {
+ // If there are no matches found, keep the popup hidden and avoid
+ // creating the view.
+ if (!aSearchResults.length) {
+ window.emit(EVENTS.FUNCTION_SEARCH_MATCH_NOT_FOUND);
+ return;
+ }
+
+ for (let item of aSearchResults) {
+ // Some function expressions don't necessarily have a name, but the
+ // parser provides us with an inferred name from an enclosing
+ // VariableDeclarator, AssignmentExpression, ObjectExpression node.
+ if (item.functionName && item.inferredName &&
+ item.functionName != item.inferredName) {
+ let s = " " + L10N.getStr("functionSearchSeparatorLabel") + " ";
+ item.displayedName = item.inferredName + s + item.functionName;
+ }
+ // The function doesn't have an explicit name, but it could be inferred.
+ else if (item.inferredName) {
+ item.displayedName = item.inferredName;
+ }
+ // The function only has an explicit name.
+ else {
+ item.displayedName = item.functionName;
+ }
+
+ // Some function expressions have unexpected bounds, since they may not
+ // necessarily have an associated name defining them.
+ if (item.inferredLocation) {
+ item.actualLocation = item.inferredLocation;
+ } else {
+ item.actualLocation = item.functionLocation;
+ }
+
+ // Create the element node for the function item.
+ let itemView = this._createItemView(
+ SourceUtils.trimUrlLength(item.displayedName + "()"),
+ SourceUtils.trimUrlLength(item.sourceUrl, 0, "start"),
+ (item.inferredChain || []).join(".")
+ );
+
+ // Append a function item to this container for each match.
+ this.push([itemView], {
+ index: -1, /* specifies on which position should the item be appended */
+ attachment: item
+ });
+ }
+
+ // There's at least one item displayed in this container. Don't select it
+ // automatically if not forced (by tests).
+ if (this._autoSelectFirstItem) {
+ this.selectedIndex = 0;
+ }
+ this.hidden = false;
+
+ // Signal that function search matches were found and displayed.
+ window.emit(EVENTS.FUNCTION_SEARCH_MATCH_FOUND);
+ },
+
+ /**
+ * The click listener for this container.
+ */
+ _onClick: function(e) {
+ let functionItem = this.getItemForElement(e.target);
+ if (functionItem) {
+ this.selectedItem = functionItem;
+ DebuggerView.Filtering.clearSearch();
+ }
+ },
+
+ /**
+ * The select listener for this container.
+ */
+ _onSelect: function({ detail: functionItem }) {
+ if (functionItem) {
+ let sourceUrl = functionItem.attachment.sourceUrl;
+ let actor = DebuggerView.Sources.getActorForLocation({ url: sourceUrl });
+ let scriptOffset = functionItem.attachment.scriptOffset;
+ let actualLocation = functionItem.attachment.actualLocation;
+
+ DebuggerView.setEditorLocation(actor, actualLocation.start.line, {
+ charOffset: scriptOffset,
+ columnOffset: actualLocation.start.column,
+ align: "center",
+ noDebug: true
+ });
+ }
+ },
+
+ _searchTimeout: null,
+ _searchFunction: null,
+ _searchedToken: ""
+});
+
+/**
+ * Preliminary setup for the DebuggerView object.
+ */
+DebuggerView.Toolbar = new ToolbarView();
+DebuggerView.Options = new OptionsView();
+DebuggerView.Filtering = new FilterView();
+DebuggerView.FilteredSources = new FilteredSourcesView();
+DebuggerView.FilteredFunctions = new FilteredFunctionsView();
+DebuggerView.StackFrames = new StackFramesView();
+DebuggerView.StackFramesClassicList = new StackFramesClassicListView();
diff --git a/toolkit/devtools/debugger/debugger-view.js b/toolkit/devtools/debugger/debugger-view.js
new file mode 100644
index 000000000..6333fe264
--- /dev/null
+++ b/toolkit/devtools/debugger/debugger-view.js
@@ -0,0 +1,847 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 1048576; // 1 MB in bytes
+const SOURCE_URL_DEFAULT_MAX_LENGTH = 64; // chars
+const STACK_FRAMES_SOURCE_URL_MAX_LENGTH = 15; // chars
+const STACK_FRAMES_SOURCE_URL_TRIM_SECTION = "center";
+const STACK_FRAMES_SCROLL_DELAY = 100; // ms
+const BREAKPOINT_LINE_TOOLTIP_MAX_LENGTH = 1000; // chars
+const BREAKPOINT_CONDITIONAL_POPUP_POSITION = "before_start";
+const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_X = 7; // px
+const BREAKPOINT_CONDITIONAL_POPUP_OFFSET_Y = -3; // px
+const RESULTS_PANEL_POPUP_POSITION = "before_end";
+const RESULTS_PANEL_MAX_RESULTS = 10;
+const FILE_SEARCH_ACTION_MAX_DELAY = 300; // ms
+const GLOBAL_SEARCH_EXPAND_MAX_RESULTS = 50;
+const GLOBAL_SEARCH_LINE_MAX_LENGTH = 300; // chars
+const GLOBAL_SEARCH_ACTION_MAX_DELAY = 1500; // ms
+const FUNCTION_SEARCH_ACTION_MAX_DELAY = 400; // ms
+const SEARCH_GLOBAL_FLAG = "!";
+const SEARCH_FUNCTION_FLAG = "@";
+const SEARCH_TOKEN_FLAG = "#";
+const SEARCH_LINE_FLAG = ":";
+const SEARCH_VARIABLE_FLAG = "*";
+const SEARCH_AUTOFILL = [SEARCH_GLOBAL_FLAG, SEARCH_FUNCTION_FLAG, SEARCH_TOKEN_FLAG];
+const EDITOR_VARIABLE_HOVER_DELAY = 750; // ms
+const EDITOR_VARIABLE_POPUP_POSITION = "topcenter bottomleft";
+const TOOLBAR_ORDER_POPUP_POSITION = "topcenter bottomleft";
+
+/**
+ * Object defining the debugger view components.
+ */
+let DebuggerView = {
+ /**
+ * Initializes the debugger view.
+ *
+ * @return object
+ * A promise that is resolved when the view finishes initializing.
+ */
+ initialize: function() {
+ if (this._startup) {
+ return this._startup;
+ }
+
+ let deferred = promise.defer();
+ this._startup = deferred.promise;
+
+ this._initializePanes();
+ this.Toolbar.initialize();
+ this.Options.initialize();
+ this.Filtering.initialize();
+ this.FilteredSources.initialize();
+ this.FilteredFunctions.initialize();
+ this.StackFrames.initialize();
+ this.StackFramesClassicList.initialize();
+ this.Sources.initialize();
+ this.VariableBubble.initialize();
+ this.Tracer.initialize();
+ this.WatchExpressions.initialize();
+ this.EventListeners.initialize();
+ this.GlobalSearch.initialize();
+ this._initializeVariablesView();
+ this._initializeEditor(deferred.resolve);
+
+ document.title = L10N.getStr("DebuggerWindowTitle");
+
+ return deferred.promise;
+ },
+
+ /**
+ * Destroys the debugger view.
+ *
+ * @return object
+ * A promise that is resolved when the view finishes destroying.
+ */
+ destroy: function() {
+ if (this._shutdown) {
+ return this._shutdown;
+ }
+
+ let deferred = promise.defer();
+ this._shutdown = deferred.promise;
+
+ this.Toolbar.destroy();
+ this.Options.destroy();
+ this.Filtering.destroy();
+ this.FilteredSources.destroy();
+ this.FilteredFunctions.destroy();
+ this.StackFrames.destroy();
+ this.StackFramesClassicList.destroy();
+ this.Sources.destroy();
+ this.VariableBubble.destroy();
+ this.Tracer.destroy();
+ this.WatchExpressions.destroy();
+ this.EventListeners.destroy();
+ this.GlobalSearch.destroy();
+ this._destroyPanes();
+ this._destroyEditor(deferred.resolve);
+
+ return deferred.promise;
+ },
+
+ /**
+ * Initializes the UI for all the displayed panes.
+ */
+ _initializePanes: function() {
+ dumpn("Initializing the DebuggerView panes");
+
+ this._body = document.getElementById("body");
+ this._editorDeck = document.getElementById("editor-deck");
+ this._sourcesPane = document.getElementById("sources-pane");
+ this._instrumentsPane = document.getElementById("instruments-pane");
+ this._instrumentsPaneToggleButton = document.getElementById("instruments-pane-toggle");
+
+ this.showEditor = this.showEditor.bind(this);
+ this.showBlackBoxMessage = this.showBlackBoxMessage.bind(this);
+ this.showProgressBar = this.showProgressBar.bind(this);
+ this.maybeShowBlackBoxMessage = this.maybeShowBlackBoxMessage.bind(this);
+
+ this._onTabSelect = this._onInstrumentsPaneTabSelect.bind(this);
+ this._instrumentsPane.tabpanels.addEventListener("select", this._onTabSelect);
+
+ this._collapsePaneString = L10N.getStr("collapsePanes");
+ this._expandPaneString = L10N.getStr("expandPanes");
+
+ this._sourcesPane.setAttribute("width", Prefs.sourcesWidth);
+ this._instrumentsPane.setAttribute("width", Prefs.instrumentsWidth);
+ this.toggleInstrumentsPane({ visible: Prefs.panesVisibleOnStartup });
+
+ // Side hosts requires a different arrangement of the debugger widgets.
+ if (gHostType == "side") {
+ this.handleHostChanged(gHostType);
+ }
+ },
+
+ /**
+ * Destroys the UI for all the displayed panes.
+ */
+ _destroyPanes: function() {
+ dumpn("Destroying the DebuggerView panes");
+
+ if (gHostType != "side") {
+ Prefs.sourcesWidth = this._sourcesPane.getAttribute("width");
+ Prefs.instrumentsWidth = this._instrumentsPane.getAttribute("width");
+ }
+
+ this._sourcesPane = null;
+ this._instrumentsPane = null;
+ this._instrumentsPaneToggleButton = null;
+ },
+
+ /**
+ * Initializes the VariablesView instance and attaches a controller.
+ */
+ _initializeVariablesView: function() {
+ this.Variables = new VariablesView(document.getElementById("variables"), {
+ searchPlaceholder: L10N.getStr("emptyVariablesFilterText"),
+ emptyText: L10N.getStr("emptyVariablesText"),
+ onlyEnumVisible: Prefs.variablesOnlyEnumVisible,
+ searchEnabled: Prefs.variablesSearchboxVisible,
+ eval: (variable, value) => {
+ let string = variable.evaluationMacro(variable, value);
+ DebuggerController.StackFrames.evaluate(string);
+ },
+ lazyEmpty: true
+ });
+
+ // Attach the current toolbox to the VView so it can link DOMNodes to
+ // the inspector/highlighter
+ this.Variables.toolbox = DebuggerController._toolbox;
+
+ // Attach a controller that handles interfacing with the debugger protocol.
+ VariablesViewController.attach(this.Variables, {
+ getEnvironmentClient: aObject => gThreadClient.environment(aObject),
+ getObjectClient: aObject => {
+ return aObject instanceof DebuggerController.Tracer.WrappedObject
+ ? DebuggerController.Tracer.syncGripClient(aObject.object)
+ : gThreadClient.pauseGrip(aObject)
+ }
+ });
+
+ // Relay events from the VariablesView.
+ this.Variables.on("fetched", (aEvent, aType) => {
+ switch (aType) {
+ case "scopes":
+ window.emit(EVENTS.FETCHED_SCOPES);
+ break;
+ case "variables":
+ window.emit(EVENTS.FETCHED_VARIABLES);
+ break;
+ case "properties":
+ window.emit(EVENTS.FETCHED_PROPERTIES);
+ break;
+ }
+ });
+ },
+
+ /**
+ * Initializes the Editor instance.
+ *
+ * @param function aCallback
+ * Called after the editor finishes initializing.
+ */
+ _initializeEditor: function(aCallback) {
+ dumpn("Initializing the DebuggerView editor");
+
+ let extraKeys = {};
+ bindKey("_doTokenSearch", "tokenSearchKey");
+ bindKey("_doGlobalSearch", "globalSearchKey", { alt: true });
+ bindKey("_doFunctionSearch", "functionSearchKey");
+ extraKeys[Editor.keyFor("jumpToLine")] = false;
+ extraKeys["Esc"] = false;
+
+ function bindKey(func, key, modifiers = {}) {
+ key = document.getElementById(key).getAttribute("key");
+ let shortcut = Editor.accel(key, modifiers);
+ extraKeys[shortcut] = () => DebuggerView.Filtering[func]();
+ }
+
+ let gutters = ["breakpoints"];
+ if (Services.prefs.getBoolPref("devtools.debugger.tracer")) {
+ gutters.unshift("hit-counts");
+ }
+
+ this.editor = new Editor({
+ mode: Editor.modes.text,
+ readOnly: true,
+ lineNumbers: true,
+ showAnnotationRuler: true,
+ gutters: gutters,
+ extraKeys: extraKeys,
+ contextMenu: "sourceEditorContextMenu",
+ enableCodeFolding: false
+ });
+
+ this.editor.appendTo(document.getElementById("editor")).then(() => {
+ this.editor.extend(DebuggerEditor);
+ this._loadingText = L10N.getStr("loadingText");
+ this._onEditorLoad(aCallback);
+ });
+
+ this.editor.on("gutterClick", (ev, line, button) => {
+ // A right-click shouldn't do anything but keep track of where
+ // it was clicked.
+ if (button == 2) {
+ this.clickedLine = line;
+ }
+ else {
+ if (this.editor.hasBreakpoint(line)) {
+ this.editor.removeBreakpoint(line);
+ } else {
+ this.editor.addBreakpoint(line);
+ }
+ }
+ });
+ },
+
+ /**
+ * The load event handler for the source editor, also executing any necessary
+ * post-load operations.
+ *
+ * @param function aCallback
+ * Called after the editor finishes loading.
+ */
+ _onEditorLoad: function(aCallback) {
+ dumpn("Finished loading the DebuggerView editor");
+
+ DebuggerController.Breakpoints.initialize().then(() => {
+ window.emit(EVENTS.EDITOR_LOADED, this.editor);
+ aCallback();
+ });
+ },
+
+ /**
+ * Destroys the Editor instance and also executes any necessary
+ * post-unload operations.
+ *
+ * @param function aCallback
+ * Called after the editor finishes destroying.
+ */
+ _destroyEditor: function(aCallback) {
+ dumpn("Destroying the DebuggerView editor");
+
+ DebuggerController.Breakpoints.destroy().then(() => {
+ window.emit(EVENTS.EDITOR_UNLOADED, this.editor);
+ this.editor.destroy();
+ this.editor = null;
+ aCallback();
+ });
+ },
+
+ /**
+ * Display the source editor.
+ */
+ showEditor: function() {
+ this._editorDeck.selectedIndex = 0;
+ },
+
+ /**
+ * Display the black box message.
+ */
+ showBlackBoxMessage: function() {
+ this._editorDeck.selectedIndex = 1;
+ },
+
+ /**
+ * Display the progress bar.
+ */
+ showProgressBar: function() {
+ this._editorDeck.selectedIndex = 2;
+ },
+
+ /**
+ * Show or hide the black box message vs. source editor depending on if the
+ * selected source is black boxed or not.
+ */
+ maybeShowBlackBoxMessage: function() {
+ let { source } = DebuggerView.Sources.selectedItem.attachment;
+ if (gThreadClient.source(source).isBlackBoxed) {
+ this.showBlackBoxMessage();
+ } else {
+ this.showEditor();
+ }
+ },
+
+ /**
+ * Sets the currently displayed text contents in the source editor.
+ * This resets the mode and undo stack.
+ *
+ * @param string aTextContent
+ * The source text content.
+ */
+ _setEditorText: function(aTextContent = "") {
+ this.editor.setMode(Editor.modes.text);
+ this.editor.setText(aTextContent);
+ this.editor.clearDebugLocation();
+ this.editor.clearHistory();
+ },
+
+ /**
+ * Sets the proper editor mode (JS or HTML) according to the specified
+ * content type, or by determining the type from the url or text content.
+ *
+ * @param string aUrl
+ * The source url.
+ * @param string aContentType [optional]
+ * The source content type.
+ * @param string aTextContent [optional]
+ * The source text content.
+ */
+ _setEditorMode: function(aUrl, aContentType = "", aTextContent = "") {
+ // Avoid setting the editor mode for very large files.
+ // Is this still necessary? See bug 929225.
+ if (aTextContent.length >= SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
+ return void this.editor.setMode(Editor.modes.text);
+ }
+
+ // Use JS mode for files with .js and .jsm extensions.
+ if (SourceUtils.isJavaScript(aUrl, aContentType)) {
+ return void this.editor.setMode(Editor.modes.js);
+ }
+
+ // Use HTML mode for files in which the first non whitespace character is
+ // &lt;, regardless of extension.
+ if (aTextContent.match(/^\s*</)) {
+ return void this.editor.setMode(Editor.modes.html);
+ }
+
+ // Unknown language, use text.
+ this.editor.setMode(Editor.modes.text);
+ },
+
+ /**
+ * Sets the currently displayed source text in the editor.
+ *
+ * You should use DebuggerView.updateEditor instead. It updates the current
+ * caret and debug location based on a requested url and line.
+ *
+ * @param object aSource
+ * The source object coming from the active thread.
+ * @param object aFlags
+ * Additional options for setting the source. Supported options:
+ * - force: boolean forcing all text to be reshown in the editor
+ * @return object
+ * A promise that is resolved after the source text has been set.
+ */
+ _setEditorSource: function(aSource, aFlags={}) {
+ // Avoid setting the same source text in the editor again.
+ if (this._editorSource.actor == aSource.actor && !aFlags.force) {
+ return this._editorSource.promise;
+ }
+ let transportType = gClient.localTransport ? "_LOCAL" : "_REMOTE";
+ let histogramId = "DEVTOOLS_DEBUGGER_DISPLAY_SOURCE" + transportType + "_MS";
+ let histogram = Services.telemetry.getHistogramById(histogramId);
+ let startTime = Date.now();
+
+ let deferred = promise.defer();
+
+ this._setEditorText(L10N.getStr("loadingText"));
+ this._editorSource = { actor: aSource.actor, promise: deferred.promise };
+
+ DebuggerController.SourceScripts.getText(aSource).then(([, aText, aContentType]) => {
+ // Avoid setting an unexpected source. This may happen when switching
+ // very fast between sources that haven't been fetched yet.
+ if (this._editorSource.actor != aSource.actor) {
+ return;
+ }
+
+ this._setEditorText(aText);
+ this._setEditorMode(aSource.url, aContentType, aText);
+
+ // Synchronize any other components with the currently displayed
+ // source.
+ DebuggerView.Sources.selectedValue = aSource.actor;
+ DebuggerController.Breakpoints.updateEditorBreakpoints();
+ DebuggerController.HitCounts.updateEditorHitCounts();
+
+ histogram.add(Date.now() - startTime);
+
+ // Resolve and notify that a source file was shown.
+ window.emit(EVENTS.SOURCE_SHOWN, aSource);
+ deferred.resolve([aSource, aText, aContentType]);
+ },
+ ([, aError]) => {
+ let msg = L10N.getStr("errorLoadingText") + DevToolsUtils.safeErrorString(aError);
+ this._setEditorText(msg);
+ Cu.reportError(msg);
+ dumpn(msg);
+
+ // Reject and notify that there was an error showing the source file.
+ window.emit(EVENTS.SOURCE_ERROR_SHOWN, aSource);
+ deferred.reject([aSource, aError]);
+ });
+
+ return deferred.promise;
+ },
+
+ /**
+ * Update the source editor's current caret and debug location based on
+ * a requested url and line.
+ *
+ * @param string aActor
+ * The target actor id.
+ * @param number aLine [optional]
+ * The target line in the source.
+ * @param object aFlags [optional]
+ * Additional options for showing the source. Supported options:
+ * - charOffset: character offset for the caret or debug location
+ * - lineOffset: line offset for the caret or debug location
+ * - columnOffset: column offset for the caret or debug location
+ * - noCaret: don't set the caret location at the specified line
+ * - noDebug: don't set the debug location at the specified line
+ * - align: string specifying whether to align the specified line
+ * at the "top", "center" or "bottom" of the editor
+ * - force: boolean forcing all text to be reshown in the editor
+ * @return object
+ * A promise that is resolved after the source text has been set.
+ */
+ setEditorLocation: function(aActor, aLine = 0, aFlags = {}) {
+ // Avoid trying to set a source for a url that isn't known yet.
+ if (!this.Sources.containsValue(aActor)) {
+ return promise.reject(new Error("Unknown source for the specified URL."));
+ }
+
+ // If the line is not specified, default to the current frame's position,
+ // if available and the frame's url corresponds to the requested url.
+ if (!aLine) {
+ let cachedFrames = DebuggerController.activeThread.cachedFrames;
+ let currentDepth = DebuggerController.StackFrames.currentFrameDepth;
+ let frame = cachedFrames[currentDepth];
+ if (frame && frame.source.actor == aActor) {
+ aLine = frame.where.line;
+ }
+ }
+
+ let sourceItem = this.Sources.getItemByValue(aActor);
+ let sourceForm = sourceItem.attachment.source;
+
+ this._editorLoc = { actor: sourceForm.actor };
+
+ // Make sure the requested source client is shown in the editor, then
+ // update the source editor's caret position and debug location.
+ return this._setEditorSource(sourceForm, aFlags).then(([,, aContentType]) => {
+ if (this._editorLoc.actor !== sourceForm.actor) {
+ return;
+ }
+
+ // Record the contentType learned from fetching
+ sourceForm.contentType = aContentType;
+ // Line numbers in the source editor should start from 1. If invalid
+ // or not specified, then don't do anything.
+ if (aLine < 1) {
+ window.emit(EVENTS.EDITOR_LOCATION_SET);
+ return;
+ }
+ if (aFlags.charOffset) {
+ aLine += this.editor.getPosition(aFlags.charOffset).line;
+ }
+ if (aFlags.lineOffset) {
+ aLine += aFlags.lineOffset;
+ }
+ if (!aFlags.noCaret) {
+ let location = { line: aLine -1, ch: aFlags.columnOffset || 0 };
+ this.editor.setCursor(location, aFlags.align);
+ }
+ if (!aFlags.noDebug) {
+ this.editor.setDebugLocation(aLine - 1);
+ }
+ window.emit(EVENTS.EDITOR_LOCATION_SET);
+ }).then(null, console.error);
+ },
+
+ /**
+ * Gets the visibility state of the instruments pane.
+ * @return boolean
+ */
+ get instrumentsPaneHidden()
+ this._instrumentsPane.hasAttribute("pane-collapsed"),
+
+ /**
+ * Gets the currently selected tab in the instruments pane.
+ * @return string
+ */
+ get instrumentsPaneTab()
+ this._instrumentsPane.selectedTab.id,
+
+ /**
+ * Sets the instruments pane hidden or visible.
+ *
+ * @param object aFlags
+ * An object containing some of the following properties:
+ * - visible: true if the pane should be shown, false to hide
+ * - animated: true to display an animation on toggle
+ * - delayed: true to wait a few cycles before toggle
+ * - callback: a function to invoke when the toggle finishes
+ * @param number aTabIndex [optional]
+ * The index of the intended selected tab in the details pane.
+ */
+ toggleInstrumentsPane: function(aFlags, aTabIndex) {
+ let pane = this._instrumentsPane;
+ let button = this._instrumentsPaneToggleButton;
+
+ ViewHelpers.togglePane(aFlags, pane);
+
+ if (aFlags.visible) {
+ button.removeAttribute("pane-collapsed");
+ button.setAttribute("tooltiptext", this._collapsePaneString);
+ } else {
+ button.setAttribute("pane-collapsed", "");
+ button.setAttribute("tooltiptext", this._expandPaneString);
+ }
+
+ if (aTabIndex !== undefined) {
+ pane.selectedIndex = aTabIndex;
+ }
+ },
+
+ /**
+ * Sets the instruments pane visible after a short period of time.
+ *
+ * @param function aCallback
+ * A function to invoke when the toggle finishes.
+ */
+ showInstrumentsPane: function(aCallback) {
+ DebuggerView.toggleInstrumentsPane({
+ visible: true,
+ animated: true,
+ delayed: true,
+ callback: aCallback
+ }, 0);
+ },
+
+ /**
+ * Handles a tab selection event on the instruments pane.
+ */
+ _onInstrumentsPaneTabSelect: function() {
+ if (this._instrumentsPane.selectedTab.id == "events-tab") {
+ DebuggerController.Breakpoints.DOM.scheduleEventListenersFetch();
+ }
+ },
+
+ /**
+ * Handles a host change event issued by the parent toolbox.
+ *
+ * @param string aType
+ * The host type, either "bottom", "side" or "window".
+ */
+ handleHostChanged: function(aType) {
+ let newLayout = "";
+
+ if (aType == "side") {
+ newLayout = "vertical";
+ this._enterVerticalLayout();
+ } else {
+ newLayout = "horizontal";
+ this._enterHorizontalLayout();
+ }
+
+ this._hostType = aType;
+ this._body.setAttribute("layout", newLayout);
+ window.emit(EVENTS.LAYOUT_CHANGED, newLayout);
+ },
+
+ /**
+ * Switches the debugger widgets to a horizontal layout.
+ */
+ _enterVerticalLayout: function() {
+ let normContainer = document.getElementById("debugger-widgets");
+ let vertContainer = document.getElementById("vertical-layout-panes-container");
+
+ // Move the soruces and instruments panes in a different container.
+ let splitter = document.getElementById("sources-and-instruments-splitter");
+ vertContainer.insertBefore(this._sourcesPane, splitter);
+ vertContainer.appendChild(this._instrumentsPane);
+
+ // Make sure the vertical layout container's height doesn't repeatedly
+ // grow or shrink based on the displayed sources, variables etc.
+ vertContainer.setAttribute("height",
+ vertContainer.getBoundingClientRect().height);
+ },
+
+ /**
+ * Switches the debugger widgets to a vertical layout.
+ */
+ _enterHorizontalLayout: function() {
+ let normContainer = document.getElementById("debugger-widgets");
+ let vertContainer = document.getElementById("vertical-layout-panes-container");
+
+ // The sources and instruments pane need to be inserted at their
+ // previous locations in their normal container.
+ let splitter = document.getElementById("sources-and-editor-splitter");
+ normContainer.insertBefore(this._sourcesPane, splitter);
+ normContainer.appendChild(this._instrumentsPane);
+
+ // Revert to the preferred sources and instruments widths, because
+ // they flexed in the vertical layout.
+ this._sourcesPane.setAttribute("width", Prefs.sourcesWidth);
+ this._instrumentsPane.setAttribute("width", Prefs.instrumentsWidth);
+ },
+
+ /**
+ * Handles any initialization on a tab navigation event issued by the client.
+ */
+ handleTabNavigation: function() {
+ dumpn("Handling tab navigation in the DebuggerView");
+
+ this.Filtering.clearSearch();
+ this.FilteredSources.clearView();
+ this.FilteredFunctions.clearView();
+ this.GlobalSearch.clearView();
+ this.StackFrames.empty();
+ this.Sources.empty();
+ this.Variables.empty();
+ this.EventListeners.empty();
+
+ if (this.editor) {
+ this.editor.setMode(Editor.modes.text);
+ this.editor.setText("");
+ this.editor.clearHistory();
+ this._editorSource = {};
+ }
+
+ this.Sources.emptyText = L10N.getStr("loadingSourcesText");
+ },
+
+ _startup: null,
+ _shutdown: null,
+ Toolbar: null,
+ Options: null,
+ Filtering: null,
+ FilteredSources: null,
+ FilteredFunctions: null,
+ GlobalSearch: null,
+ StackFrames: null,
+ Sources: null,
+ Tracer: null,
+ Variables: null,
+ VariableBubble: null,
+ WatchExpressions: null,
+ EventListeners: null,
+ editor: null,
+ _editorSource: {},
+ _loadingText: "",
+ _body: null,
+ _editorDeck: null,
+ _sourcesPane: null,
+ _instrumentsPane: null,
+ _instrumentsPaneToggleButton: null,
+ _collapsePaneString: "",
+ _expandPaneString: ""
+};
+
+/**
+ * A custom items container, used for displaying views like the
+ * FilteredSources, FilteredFunctions etc., inheriting the generic WidgetMethods.
+ */
+function ResultsPanelContainer() {
+}
+
+ResultsPanelContainer.prototype = Heritage.extend(WidgetMethods, {
+ /**
+ * Sets the anchor node for this container panel.
+ * @param nsIDOMNode aNode
+ */
+ set anchor(aNode) {
+ this._anchor = aNode;
+
+ // If the anchor node is not null, create a panel to attach to the anchor
+ // when showing the popup.
+ if (aNode) {
+ if (!this._panel) {
+ this._panel = document.createElement("panel");
+ this._panel.id = "results-panel";
+ this._panel.setAttribute("level", "top");
+ this._panel.setAttribute("noautofocus", "true");
+ this._panel.setAttribute("consumeoutsideclicks", "false");
+ document.documentElement.appendChild(this._panel);
+ }
+ if (!this.widget) {
+ this.widget = new SimpleListWidget(this._panel);
+ this.autoFocusOnFirstItem = false;
+ this.autoFocusOnSelection = false;
+ this.maintainSelectionVisible = false;
+ }
+ }
+ // Cleanup the anchor and remove the previously created panel.
+ else {
+ this._panel.remove();
+ this._panel = null;
+ this.widget = null;
+ }
+ },
+
+ /**
+ * Gets the anchor node for this container panel.
+ * @return nsIDOMNode
+ */
+ get anchor() {
+ return this._anchor;
+ },
+
+ /**
+ * Sets the container panel hidden or visible. It's hidden by default.
+ * @param boolean aFlag
+ */
+ set hidden(aFlag) {
+ if (aFlag) {
+ this._panel.hidden = true;
+ this._panel.hidePopup();
+ } else {
+ this._panel.hidden = false;
+ this._panel.openPopup(this._anchor, this.position, this.left, this.top);
+ }
+ },
+
+ /**
+ * Gets this container's visibility state.
+ * @return boolean
+ */
+ get hidden()
+ this._panel.state == "closed" ||
+ this._panel.state == "hiding",
+
+ /**
+ * Removes all items from this container and hides it.
+ */
+ clearView: function() {
+ this.hidden = true;
+ this.empty();
+ },
+
+ /**
+ * Selects the next found item in this container.
+ * Does not change the currently focused node.
+ */
+ selectNext: function() {
+ let nextIndex = this.selectedIndex + 1;
+ if (nextIndex >= this.itemCount) {
+ nextIndex = 0;
+ }
+ this.selectedItem = this.getItemAtIndex(nextIndex);
+ },
+
+ /**
+ * Selects the previously found item in this container.
+ * Does not change the currently focused node.
+ */
+ selectPrev: function() {
+ let prevIndex = this.selectedIndex - 1;
+ if (prevIndex < 0) {
+ prevIndex = this.itemCount - 1;
+ }
+ this.selectedItem = this.getItemAtIndex(prevIndex);
+ },
+
+ /**
+ * Customization function for creating an item's UI.
+ *
+ * @param string aLabel
+ * The item's label string.
+ * @param string aBeforeLabel
+ * An optional string shown before the label.
+ * @param string aBelowLabel
+ * An optional string shown underneath the label.
+ */
+ _createItemView: function(aLabel, aBelowLabel, aBeforeLabel) {
+ let container = document.createElement("vbox");
+ container.className = "results-panel-item";
+
+ let firstRowLabels = document.createElement("hbox");
+ let secondRowLabels = document.createElement("hbox");
+
+ if (aBeforeLabel) {
+ let beforeLabelNode = document.createElement("label");
+ beforeLabelNode.className = "plain results-panel-item-label-before";
+ beforeLabelNode.setAttribute("value", aBeforeLabel);
+ firstRowLabels.appendChild(beforeLabelNode);
+ }
+
+ let labelNode = document.createElement("label");
+ labelNode.className = "plain results-panel-item-label";
+ labelNode.setAttribute("value", aLabel);
+ firstRowLabels.appendChild(labelNode);
+
+ if (aBelowLabel) {
+ let belowLabelNode = document.createElement("label");
+ belowLabelNode.className = "plain results-panel-item-label-below";
+ belowLabelNode.setAttribute("value", aBelowLabel);
+ secondRowLabels.appendChild(belowLabelNode);
+ }
+
+ container.appendChild(firstRowLabels);
+ container.appendChild(secondRowLabels);
+
+ return container;
+ },
+
+ _anchor: null,
+ _panel: null,
+ position: RESULTS_PANEL_POPUP_POSITION,
+ left: 0,
+ top: 0
+});
diff --git a/toolkit/devtools/debugger/debugger.css b/toolkit/devtools/debugger/debugger.css
new file mode 100644
index 000000000..13eab6096
--- /dev/null
+++ b/toolkit/devtools/debugger/debugger.css
@@ -0,0 +1,50 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Side pane views */
+
+#sources-pane > tabpanels > tabpanel,
+#instruments-pane > tabpanels > tabpanel {
+ -moz-box-orient: vertical;
+}
+
+/* Toolbar controls */
+
+.devtools-toolbarbutton:not([label]) > .toolbarbutton-text {
+ display: none;
+}
+
+/* Horizontal vs. vertical layout */
+
+#body[layout=vertical] #debugger-widgets {
+ -moz-box-orient: vertical;
+}
+
+#body[layout=vertical] #sources-pane {
+ -moz-box-flex: 1;
+}
+
+#body[layout=vertical] #instruments-pane {
+ -moz-box-flex: 2;
+}
+
+#body[layout=vertical] #instruments-pane-toggle {
+ display: none;
+}
+
+#body[layout=vertical] #sources-and-editor-splitter,
+#body[layout=vertical] #editor-and-instruments-splitter {
+ display: none;
+}
+
+#body[layout=horizontal] #vertical-layout-splitter,
+#body[layout=horizontal] #vertical-layout-panes-container {
+ display: none;
+}
+
+#body[layout=vertical] #stackframes {
+ visibility: hidden;
+}
diff --git a/toolkit/devtools/debugger/debugger.xul b/toolkit/devtools/debugger/debugger.xul
new file mode 100644
index 000000000..eb42add05
--- /dev/null
+++ b/toolkit/devtools/debugger/debugger.xul
@@ -0,0 +1,524 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/devtools/debugger.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/debugger.css" type="text/css"?>
+<!DOCTYPE window [
+ <!ENTITY % debuggerDTD SYSTEM "chrome://browser/locale/devtools/debugger.dtd">
+ %debuggerDTD;
+]>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ macanimationtype="document"
+ fullscreenbutton="true"
+ screenX="4" screenY="4"
+ width="960" height="480"
+ persist="screenX screenY width height sizemode">
+
+ <script type="application/javascript;version=1.8"
+ src="chrome://browser/content/devtools/theme-switching.js"/>
+ <script type="text/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="text/javascript" src="debugger-controller.js"/>
+ <script type="text/javascript" src="debugger-view.js"/>
+ <script type="text/javascript" src="debugger-toolbar.js"/>
+ <script type="text/javascript" src="debugger-panes.js"/>
+
+ <commandset id="editMenuCommands"/>
+
+ <commandset id="debuggerCommands">
+ <command id="blackBoxCommand"
+ oncommand="DebuggerView.Sources.toggleBlackBoxing()"/>
+ <command id="unBlackBoxButton"
+ oncommand="DebuggerView.Sources._onStopBlackBoxing()"/>
+ <command id="prettyPrintCommand"
+ oncommand="DebuggerView.Sources.togglePrettyPrint()"/>
+ <command id="toggleBreakpointsCommand"
+ oncommand="DebuggerView.Sources.toggleBreakpoints()"/>
+ <command id="nextSourceCommand"
+ oncommand="DebuggerView.Sources.selectNextItem()"/>
+ <command id="prevSourceCommand"
+ oncommand="DebuggerView.Sources.selectPrevItem()"/>
+ <command id="resumeCommand"
+ oncommand="DebuggerView.Toolbar._onResumePressed()"/>
+ <command id="stepOverCommand"
+ oncommand="DebuggerView.Toolbar._onStepOverPressed()"/>
+ <command id="stepInCommand"
+ oncommand="DebuggerView.Toolbar._onStepInPressed()"/>
+ <command id="stepOutCommand"
+ oncommand="DebuggerView.Toolbar._onStepOutPressed()"/>
+ <command id="fileSearchCommand"
+ oncommand="DebuggerView.Filtering._doFileSearch()"/>
+ <command id="globalSearchCommand"
+ oncommand="DebuggerView.Filtering._doGlobalSearch()"/>
+ <command id="functionSearchCommand"
+ oncommand="DebuggerView.Filtering._doFunctionSearch()"/>
+ <command id="tokenSearchCommand"
+ oncommand="DebuggerView.Filtering._doTokenSearch()"/>
+ <command id="lineSearchCommand"
+ oncommand="DebuggerView.Filtering._doLineSearch()"/>
+ <command id="variableSearchCommand"
+ oncommand="DebuggerView.Filtering._doVariableSearch()"/>
+ <command id="variablesFocusCommand"
+ oncommand="DebuggerView.Filtering._doVariablesFocus()"/>
+ <command id="addBreakpointCommand"
+ oncommand="DebuggerView.Sources._onCmdAddBreakpoint(event)"/>
+ <command id="addConditionalBreakpointCommand"
+ oncommand="DebuggerView.Sources._onCmdAddConditionalBreakpoint(event)"/>
+ <command id="addWatchExpressionCommand"
+ oncommand="DebuggerView.WatchExpressions._onCmdAddExpression()"/>
+ <command id="removeAllWatchExpressionsCommand"
+ oncommand="DebuggerView.WatchExpressions._onCmdRemoveAllExpressions()"/>
+ <command id="toggleAutoPrettyPrint"
+ oncommand="DebuggerView.Options._toggleAutoPrettyPrint()"/>
+ <command id="togglePauseOnExceptions"
+ oncommand="DebuggerView.Options._togglePauseOnExceptions()"/>
+ <command id="toggleIgnoreCaughtExceptions"
+ oncommand="DebuggerView.Options._toggleIgnoreCaughtExceptions()"/>
+ <command id="toggleShowPanesOnStartup"
+ oncommand="DebuggerView.Options._toggleShowPanesOnStartup()"/>
+ <command id="toggleShowOnlyEnum"
+ oncommand="DebuggerView.Options._toggleShowVariablesOnlyEnum()"/>
+ <command id="toggleShowVariablesFilterBox"
+ oncommand="DebuggerView.Options._toggleShowVariablesFilterBox()"/>
+ <command id="toggleShowOriginalSource"
+ oncommand="DebuggerView.Options._toggleShowOriginalSource()"/>
+ <command id="toggleAutoBlackBox"
+ oncommand="DebuggerView.Options._toggleAutoBlackBox()"/>
+ <command id="toggleTracing"
+ oncommand="DebuggerView.Tracer._onToggleTracing()"/>
+ <command id="startTracing"
+ oncommand="DebuggerView.Tracer._onStartTracing()"/>
+ <command id="clearTraces"
+ oncommand="DebuggerView.Tracer._onClear()"/>
+ </commandset>
+
+ <popupset id="debuggerPopupset">
+ <menupopup id="sourceEditorContextMenu"
+ onpopupshowing="goUpdateGlobalEditMenuItems()">
+ <menuitem id="se-dbg-cMenu-addBreakpoint"
+ label="&debuggerUI.seMenuBreak;"
+ key="addBreakpointKey"
+ command="addBreakpointCommand"/>
+ <menuitem id="se-dbg-cMenu-addConditionalBreakpoint"
+ label="&debuggerUI.seMenuCondBreak;"
+ key="addConditionalBreakpointKey"
+ command="addConditionalBreakpointCommand"/>
+ <menuitem id="se-dbg-cMenu-addAsWatch"
+ label="&debuggerUI.seMenuAddWatch;"
+ key="addWatchExpressionKey"
+ command="addWatchExpressionCommand"/>
+ <menuseparator/>
+ <menuitem id="cMenu_copy"/>
+ <menuseparator/>
+ <menuitem id="cMenu_selectAll"/>
+ <menuseparator/>
+ <menuitem id="se-dbg-cMenu-findFile"
+ label="&debuggerUI.searchFile;"
+ accesskey="&debuggerUI.searchFile.accesskey;"
+ key="fileSearchKey"
+ command="fileSearchCommand"/>
+ <menuitem id="se-dbg-cMenu-findGlobal"
+ label="&debuggerUI.searchGlobal;"
+ accesskey="&debuggerUI.searchGlobal.accesskey;"
+ key="globalSearchKey"
+ command="globalSearchCommand"/>
+ <menuitem id="se-dbg-cMenu-findFunction"
+ label="&debuggerUI.searchFunction;"
+ accesskey="&debuggerUI.searchFunction.accesskey;"
+ key="functionSearchKey"
+ command="functionSearchCommand"/>
+ <menuseparator/>
+ <menuitem id="se-dbg-cMenu-findToken"
+ label="&debuggerUI.searchToken;"
+ accesskey="&debuggerUI.searchToken.accesskey;"
+ key="tokenSearchKey"
+ command="tokenSearchCommand"/>
+ <menuitem id="se-dbg-cMenu-findLine"
+ label="&debuggerUI.searchGoToLine;"
+ accesskey="&debuggerUI.searchGoToLine.accesskey;"
+ key="lineSearchKey"
+ command="lineSearchCommand"/>
+ <menuseparator/>
+ <menuitem id="se-dbg-cMenu-findVariable"
+ label="&debuggerUI.searchVariable;"
+ accesskey="&debuggerUI.searchVariable.accesskey;"
+ key="variableSearchKey"
+ command="variableSearchCommand"/>
+ <menuitem id="se-dbg-cMenu-focusVariables"
+ label="&debuggerUI.focusVariables;"
+ accesskey="&debuggerUI.focusVariables.accesskey;"
+ key="variablesFocusKey"
+ command="variablesFocusCommand"/>
+ <menuitem id="se-dbg-cMenu-prettyPrint"
+ label="&debuggerUI.sources.prettyPrint;"
+ command="prettyPrintCommand"/>
+ </menupopup>
+ <menupopup id="debuggerWatchExpressionsContextMenu">
+ <menuitem id="add-watch-expression"
+ label="&debuggerUI.addWatch;"
+ accesskey="&debuggerUI.addWatch.accesskey;"
+ key="addWatchExpressionKey"
+ command="addWatchExpressionCommand"/>
+ <menuitem id="removeAll-watch-expression"
+ label="&debuggerUI.removeAllWatch;"
+ accesskey="&debuggerUI.removeAllWatch.accesskey;"
+ key="removeAllWatchExpressionsKey"
+ command="removeAllWatchExpressionsCommand"/>
+ </menupopup>
+ <menupopup id="debuggerPrefsContextMenu"
+ position="before_end"
+ onpopupshowing="DebuggerView.Options._onPopupShowing()"
+ onpopuphiding="DebuggerView.Options._onPopupHiding()"
+ onpopuphidden="DebuggerView.Options._onPopupHidden()">
+ <menuitem id="auto-pretty-print"
+ type="checkbox"
+ label="&debuggerUI.autoPrettyPrint;"
+ accesskey="&debuggerUI.autoPrettyPrint.accesskey;"
+ command="toggleAutoPrettyPrint"/>
+ <menuitem id="pause-on-exceptions"
+ type="checkbox"
+ label="&debuggerUI.pauseExceptions;"
+ accesskey="&debuggerUI.pauseExceptions.accesskey;"
+ command="togglePauseOnExceptions"/>
+ <menuitem id="ignore-caught-exceptions"
+ type="checkbox"
+ label="&debuggerUI.ignoreCaughtExceptions;"
+ accesskey="&debuggerUI.ignoreCaughtExceptions.accesskey;"
+ command="toggleIgnoreCaughtExceptions"/>
+ <menuitem id="show-panes-on-startup"
+ type="checkbox"
+ label="&debuggerUI.showPanesOnInit;"
+ accesskey="&debuggerUI.showPanesOnInit.accesskey;"
+ command="toggleShowPanesOnStartup"/>
+ <menuitem id="show-vars-only-enum"
+ type="checkbox"
+ label="&debuggerUI.showOnlyEnum;"
+ accesskey="&debuggerUI.showOnlyEnum.accesskey;"
+ command="toggleShowOnlyEnum"/>
+ <menuitem id="show-vars-filter-box"
+ type="checkbox"
+ label="&debuggerUI.showVarsFilter;"
+ accesskey="&debuggerUI.showVarsFilter.accesskey;"
+ command="toggleShowVariablesFilterBox"/>
+ <menuitem id="show-original-source"
+ type="checkbox"
+ label="&debuggerUI.showOriginalSource;"
+ accesskey="&debuggerUI.showOriginalSource.accesskey;"
+ command="toggleShowOriginalSource"/>
+ <menuitem id="auto-black-box"
+ type="checkbox"
+ label="&debuggerUI.autoBlackBox;"
+ accesskey="&debuggerUI.autoBlackBox.accesskey;"
+ command="toggleAutoBlackBox"/>
+ </menupopup>
+ </popupset>
+
+ <keyset id="debuggerKeys">
+ <key id="nextSourceKey"
+ keycode="VK_DOWN"
+ modifiers="accel alt"
+ command="nextSourceCommand"/>
+ <key id="prevSourceKey"
+ keycode="VK_UP"
+ modifiers="accel alt"
+ command="prevSourceCommand"/>
+ <key id="resumeKey"
+ keycode="&debuggerUI.stepping.resume1;"
+ command="resumeCommand"/>
+ <key id="resumeKey2"
+ keycode="&debuggerUI.stepping.resume2;"
+ modifiers="accel"
+ command="resumeCommand"/>
+ <key id="stepOverKey"
+ keycode="&debuggerUI.stepping.stepOver1;"
+ command="stepOverCommand"/>
+ <key id="stepOverKey2"
+ keycode="&debuggerUI.stepping.stepOver2;"
+ modifiers="accel"
+ command="stepOverCommand"/>
+ <key id="stepInKey"
+ keycode="&debuggerUI.stepping.stepIn1;"
+ command="stepInCommand"/>
+ <key id="stepInKey2"
+ keycode="&debuggerUI.stepping.stepIn2;"
+ modifiers="accel"
+ command="stepInCommand"/>
+ <key id="stepOutKey"
+ keycode="&debuggerUI.stepping.stepOut1;"
+ modifiers="shift"
+ command="stepOutCommand"/>
+ <key id="stepOutKey2"
+ keycode="&debuggerUI.stepping.stepOut2;"
+ modifiers="accel shift"
+ command="stepOutCommand"/>
+ <key id="fileSearchKey"
+ key="&debuggerUI.searchFile.key;"
+ modifiers="accel"
+ command="fileSearchCommand"/>
+ <key id="fileSearchKey"
+ key="&debuggerUI.searchFile.altkey;"
+ modifiers="accel"
+ command="fileSearchCommand"/>
+ <key id="globalSearchKey"
+ key="&debuggerUI.searchGlobal.key;"
+ modifiers="accel alt"
+ command="globalSearchCommand"/>
+ <key id="functionSearchKey"
+ key="&debuggerUI.searchFunction.key;"
+ modifiers="accel"
+ command="functionSearchCommand"/>
+ <key id="tokenSearchKey"
+ key="&debuggerUI.searchToken.key;"
+ modifiers="accel"
+ command="tokenSearchCommand"/>
+ <key id="lineSearchKey"
+ key="&debuggerUI.searchGoToLine.key;"
+ modifiers="accel"
+ command="lineSearchCommand"/>
+ <key id="variableSearchKey"
+ key="&debuggerUI.searchVariable.key;"
+ modifiers="accel alt"
+ command="variableSearchCommand"/>
+ <key id="variablesFocusKey"
+ key="&debuggerUI.focusVariables.key;"
+ modifiers="accel shift"
+ command="variablesFocusCommand"/>
+ <key id="addBreakpointKey"
+ key="&debuggerUI.seMenuBreak.key;"
+ modifiers="accel"
+ command="addBreakpointCommand"/>
+ <key id="addConditionalBreakpointKey"
+ key="&debuggerUI.seMenuCondBreak.key;"
+ modifiers="accel shift"
+ command="addConditionalBreakpointCommand"/>
+ <key id="addWatchExpressionKey"
+ key="&debuggerUI.seMenuAddWatch.key;"
+ modifiers="accel shift"
+ command="addWatchExpressionCommand"/>
+ <key id="removeAllWatchExpressionsKey"
+ key="&debuggerUI.removeAllWatch.key;"
+ modifiers="accel alt"
+ command="removeAllWatchExpressionsCommand"/>
+ </keyset>
+
+ <vbox id="body"
+ class="theme-body"
+ layout="horizontal"
+ flex="1">
+ <toolbar id="debugger-toolbar"
+ class="devtools-toolbar">
+ <hbox id="debugger-controls"
+ class="devtools-toolbarbutton-group">
+ <toolbarbutton id="resume"
+ class="devtools-toolbarbutton"
+ tabindex="0"/>
+ <toolbarbutton id="step-over"
+ class="devtools-toolbarbutton"
+ tabindex="0"/>
+ <toolbarbutton id="step-in"
+ class="devtools-toolbarbutton"
+ tabindex="0"/>
+ <toolbarbutton id="step-out"
+ class="devtools-toolbarbutton"
+ tabindex="0"/>
+ </hbox>
+ <hbox>
+ <toolbarbutton id="trace"
+ class="devtools-toolbarbutton"
+ command="toggleTracing"
+ tabindex="0"
+ hidden="true"/>
+ </hbox>
+ <vbox id="stackframes" flex="1"/>
+ <textbox id="searchbox"
+ class="devtools-searchinput" type="search"/>
+ <toolbarbutton id="instruments-pane-toggle"
+ class="devtools-toolbarbutton"
+ tooltiptext="&debuggerUI.panesButton.tooltip;"
+ tabindex="0"/>
+ <toolbarbutton id="debugger-options"
+ class="devtools-toolbarbutton devtools-option-toolbarbutton"
+ tooltiptext="&debuggerUI.optsButton.tooltip;"
+ popup="debuggerPrefsContextMenu"
+ tabindex="0"/>
+ </toolbar>
+ <vbox id="globalsearch" orient="vertical" hidden="true"/>
+ <splitter class="devtools-horizontal-splitter" hidden="true"/>
+ <hbox id="debugger-widgets" flex="1">
+ <tabbox id="sources-pane"
+ class="devtools-sidebar-tabs">
+ <tabs>
+ <tab id="sources-tab" label="&debuggerUI.tabs.sources;"/>
+ <tab id="callstack-tab" label="&debuggerUI.tabs.callstack;"/>
+ <tab id="tracer-tab" label="&debuggerUI.tabs.traces;" hidden="true"/>
+ </tabs>
+ <tabpanels flex="1">
+ <tabpanel id="sources-tabpanel">
+ <vbox id="sources" flex="1"/>
+ <toolbar id="sources-toolbar" class="devtools-toolbar">
+ <hbox id="sources-controls"
+ class="devtools-toolbarbutton-group">
+ <toolbarbutton id="black-box"
+ class="devtools-toolbarbutton"
+ tooltiptext="&debuggerUI.sources.blackBoxTooltip;"
+ command="blackBoxCommand"/>
+ <toolbarbutton id="pretty-print"
+ class="devtools-toolbarbutton"
+ tooltiptext="&debuggerUI.sources.prettyPrint;"
+ command="prettyPrintCommand"
+ hidden="true"/>
+ </hbox>
+ <vbox class="devtools-separator"/>
+ <toolbarbutton id="toggle-breakpoints"
+ class="devtools-toolbarbutton"
+ tooltiptext="&debuggerUI.sources.toggleBreakpoints;"
+ command="toggleBreakpointsCommand"/>
+ </toolbar>
+ </tabpanel>
+ <tabpanel id="callstack-tabpanel">
+ <vbox id="callstack-list" flex="1"/>
+ </tabpanel>
+ <tabpanel id="tracer-tabpanel">
+ <vbox id="tracer-traces" flex="1"/>
+ <hbox class="trace-item-template" hidden="true">
+ <hbox class="trace-item" align="center" flex="1" crop="end">
+ <label class="trace-type plain"/>
+ <label class="trace-name plain" crop="end"/>
+ </hbox>
+ </hbox>
+ <toolbar id="tracer-toolbar" class="devtools-toolbar">
+ <toolbarbutton id="clear-tracer"
+ label="&debuggerUI.clearButton;"
+ tooltiptext="&debuggerUI.clearButton.tooltip;"
+ command="clearTraces"
+ class="devtools-toolbarbutton"/>
+ <textbox id="tracer-search"
+ class="devtools-searchinput"
+ flex="1"
+ type="search"/>
+ </toolbar>
+ </tabpanel>
+ </tabpanels>
+ </tabbox>
+ <splitter id="sources-and-editor-splitter"
+ class="devtools-side-splitter"/>
+ <deck id="editor-deck" flex="1" class="devtools-main-content">
+ <vbox id="editor"/>
+ <vbox id="black-boxed-message"
+ align="center"
+ pack="center">
+ <description id="black-boxed-message-label">
+ &debuggerUI.blackBoxMessage.label;
+ </description>
+ <button id="black-boxed-message-button"
+ class="devtools-toolbarbutton"
+ label="&debuggerUI.blackBoxMessage.unBlackBoxButton;"
+ command="unBlackBoxCommand"/>
+ </vbox>
+ <vbox id="source-progress-container"
+ align="center"
+ pack="center">
+ <progressmeter id="source-progress"
+ mode="undetermined"/>
+ </vbox>
+ </deck>
+ <splitter id="editor-and-instruments-splitter"
+ class="devtools-side-splitter"/>
+ <tabbox id="instruments-pane"
+ class="devtools-sidebar-tabs"
+ hidden="true">
+ <tabs>
+ <tab id="variables-tab" label="&debuggerUI.tabs.variables;"/>
+ <tab id="events-tab" label="&debuggerUI.tabs.events;"/>
+ </tabs>
+ <tabpanels flex="1">
+ <tabpanel id="variables-tabpanel">
+ <vbox id="expressions"/>
+ <splitter class="devtools-horizontal-splitter"/>
+ <vbox id="variables" flex="1"/>
+ </tabpanel>
+ <tabpanel id="events-tabpanel">
+ <vbox id="event-listeners" flex="1"/>
+ </tabpanel>
+ </tabpanels>
+ </tabbox>
+ <splitter id="vertical-layout-splitter"
+ class="devtools-horizontal-splitter"/>
+ <hbox id="vertical-layout-panes-container">
+ <splitter id="sources-and-instruments-splitter"
+ class="devtools-side-splitter"/>
+ <!-- The sources-pane and instruments-pane will be moved in this
+ container if the toolbox's host requires it. -->
+ </hbox>
+ </hbox>
+ </vbox>
+
+ <panel id="searchbox-help-panel"
+ level="top"
+ type="arrow"
+ position="before_start"
+ noautofocus="true"
+ consumeoutsideclicks="false">
+ <vbox>
+ <hbox>
+ <label id="filter-label"/>
+ </hbox>
+ <label id="searchbox-panel-operators"
+ value="&debuggerUI.searchPanelOperators;"/>
+ <hbox align="center">
+ <button id="global-operator-button"
+ class="searchbox-panel-operator-button devtools-monospace"
+ command="globalSearchCommand"/>
+ <label id="global-operator-label"
+ class="plain searchbox-panel-operator-label"/>
+ </hbox>
+ <hbox align="center">
+ <button id="function-operator-button"
+ class="searchbox-panel-operator-button devtools-monospace"
+ command="functionSearchCommand"/>
+ <label id="function-operator-label"
+ class="plain searchbox-panel-operator-label"/>
+ </hbox>
+ <hbox align="center">
+ <button id="token-operator-button"
+ class="searchbox-panel-operator-button devtools-monospace"
+ command="tokenSearchCommand"/>
+ <label id="token-operator-label"
+ class="plain searchbox-panel-operator-label"/>
+ </hbox>
+ <hbox align="center">
+ <button id="line-operator-button"
+ class="searchbox-panel-operator-button devtools-monospace"
+ command="lineSearchCommand"/>
+ <label id="line-operator-label"
+ class="plain searchbox-panel-operator-label"/>
+ </hbox>
+ <hbox align="center">
+ <button id="variable-operator-button"
+ class="searchbox-panel-operator-button devtools-monospace"
+ command="variableSearchCommand"/>
+ <label id="variable-operator-label"
+ class="plain searchbox-panel-operator-label"/>
+ </hbox>
+ </vbox>
+ </panel>
+
+ <panel id="conditional-breakpoint-panel"
+ level="top"
+ type="arrow"
+ noautofocus="true"
+ consumeoutsideclicks="false">
+ <vbox>
+ <label id="conditional-breakpoint-panel-description"
+ value="&debuggerUI.condBreakPanelTitle;"/>
+ <textbox id="conditional-breakpoint-panel-textbox"/>
+ </vbox>
+ </panel>
+
+</window>
diff --git a/toolkit/devtools/debugger/moz.build b/toolkit/devtools/debugger/moz.build
new file mode 100644
index 000000000..8276e2982
--- /dev/null
+++ b/toolkit/devtools/debugger/moz.build
@@ -0,0 +1,11 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXTRA_JS_MODULES.devtools.debugger += [
+ 'debugger-commands.js',
+ 'panel.js'
+]
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
diff --git a/toolkit/devtools/debugger/panel.js b/toolkit/devtools/debugger/panel.js
new file mode 100644
index 000000000..1411c3e41
--- /dev/null
+++ b/toolkit/devtools/debugger/panel.js
@@ -0,0 +1,126 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Cc, Ci, Cu, Cr } = require("chrome");
+const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
+const EventEmitter = require("devtools/toolkit/event-emitter");
+const { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
+
+function DebuggerPanel(iframeWindow, toolbox) {
+ this.panelWin = iframeWindow;
+ this._toolbox = toolbox;
+ this._destroyer = null;
+
+ this._view = this.panelWin.DebuggerView;
+ this._controller = this.panelWin.DebuggerController;
+ this._view._hostType = this._toolbox.hostType;
+ this._controller._target = this.target;
+ this._controller._toolbox = this._toolbox;
+
+ this.handleHostChanged = this.handleHostChanged.bind(this);
+ this.highlightWhenPaused = this.highlightWhenPaused.bind(this);
+ this.unhighlightWhenResumed = this.unhighlightWhenResumed.bind(this);
+
+ EventEmitter.decorate(this);
+};
+
+exports.DebuggerPanel = DebuggerPanel;
+
+DebuggerPanel.prototype = {
+ /**
+ * Open is effectively an asynchronous constructor.
+ *
+ * @return object
+ * A promise that is resolved when the Debugger completes opening.
+ */
+ open: function() {
+ let targetPromise;
+
+ // Local debugging needs to make the target remote.
+ if (!this.target.isRemote) {
+ targetPromise = this.target.makeRemote();
+ // Listen for tab switching events to manage focus when the content window
+ // is paused and events suppressed.
+ this.target.tab.addEventListener('TabSelect', this);
+ } else {
+ targetPromise = promise.resolve(this.target);
+ }
+
+ return targetPromise
+ .then(() => this._controller.startupDebugger())
+ .then(() => this._controller.connect())
+ .then(() => {
+ this._toolbox.on("host-changed", this.handleHostChanged);
+ this.target.on("thread-paused", this.highlightWhenPaused);
+ this.target.on("thread-resumed", this.unhighlightWhenResumed);
+ this.isReady = true;
+ this.emit("ready");
+ return this;
+ })
+ .then(null, function onError(aReason) {
+ DevToolsUtils.reportException("DebuggerPanel.prototype.open", aReason);
+ });
+ },
+
+ // DevToolPanel API
+
+ get target() this._toolbox.target,
+
+ destroy: function() {
+ // Make sure this panel is not already destroyed.
+ if (this._destroyer) {
+ return this._destroyer;
+ }
+
+ this.target.off("thread-paused", this.highlightWhenPaused);
+ this.target.off("thread-resumed", this.unhighlightWhenResumed);
+
+ if (!this.target.isRemote) {
+ this.target.tab.removeEventListener('TabSelect', this);
+ }
+
+ return this._destroyer = this._controller.shutdownDebugger().then(() => {
+ this.emit("destroyed");
+ });
+ },
+
+ // DebuggerPanel API
+
+ addBreakpoint: function(aLocation, aOptions) {
+ return this._controller.Breakpoints.addBreakpoint(aLocation, aOptions);
+ },
+
+ removeBreakpoint: function(aLocation) {
+ return this._controller.Breakpoints.removeBreakpoint(aLocation);
+ },
+
+ handleHostChanged: function() {
+ this._view.handleHostChanged(this._toolbox.hostType);
+ },
+
+ highlightWhenPaused: function() {
+ this._toolbox.highlightTool("jsdebugger");
+
+ // Also raise the toolbox window if it is undocked or select the
+ // corresponding tab when toolbox is docked.
+ this._toolbox.raise();
+ },
+
+ unhighlightWhenResumed: function() {
+ this._toolbox.unhighlightTool("jsdebugger");
+ },
+
+ // nsIDOMEventListener API
+
+ handleEvent: function(aEvent) {
+ if (aEvent.target == this.target.tab &&
+ this._controller.activeThread.state == "paused") {
+ // Wait a tick for the content focus event to be delivered.
+ DevToolsUtils.executeSoon(() => this._toolbox.focusTool("jsdebugger"));
+ }
+ }
+};
diff --git a/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon3/lib/main.js b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon3/lib/main.js
new file mode 100644
index 000000000..fc00b60a1
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon3/package.json b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon3/package.json
new file mode 100644
index 000000000..4bf1bed50
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/bootstrap.js b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/bootstrap.js
new file mode 100644
index 000000000..360468ab2
--- /dev/null
+++ b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/bootstrap.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { interfaces: Ci, classes: Cc, utils: Cu } = Components;
+
+function notify() {
+ // Log objects so makeDebuggeeValue can get the global to use
+ console.log({ msg: "Hello again" });
+}
+
+function startup(aParams, aReason) {
+ 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");
+ // 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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/chrome.manifest b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/chrome.manifest
new file mode 100644
index 000000000..ccb88ddf1
--- /dev/null
+++ b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/chrome.manifest
@@ -0,0 +1 @@
+content browser_dbg_addon4 .
diff --git a/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/install.rdf b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/install.rdf
new file mode 100644
index 000000000..45679ffc9
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/test.jsm b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/test.jsm
new file mode 100644
index 000000000..17bebfd8e
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/test.xul b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/test.xul
new file mode 100644
index 000000000..733817ad8
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/test2.jsm b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/test2.jsm
new file mode 100644
index 000000000..703869f43
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/test2.xul b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/test2.xul
new file mode 100644
index 000000000..372d05587
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/testxul.js b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/testxul.js
new file mode 100644
index 000000000..7ac4eabc7
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/testxul2.js b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon4/testxul2.js
new file mode 100644
index 000000000..7ac4eabc7
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/bootstrap.js b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/bootstrap.js
new file mode 100644
index 000000000..c8f89bd34
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/ */
+
+const { 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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/chrome.manifest b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/chrome.manifest
new file mode 100644
index 000000000..ceef8d06d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/chrome.manifest
@@ -0,0 +1 @@
+content browser_dbg_addon5 .
diff --git a/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/install.rdf b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/install.rdf
new file mode 100644
index 000000000..af2cbbb5d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/test.jsm b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/test.jsm
new file mode 100644
index 000000000..17bebfd8e
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/test.xul b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/test.xul
new file mode 100644
index 000000000..733817ad8
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/test2.jsm b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/test2.jsm
new file mode 100644
index 000000000..703869f43
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/test2.xul b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/test2.xul
new file mode 100644
index 000000000..372d05587
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/testxul.js b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/testxul.js
new file mode 100644
index 000000000..7ac4eabc7
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/testxul2.js b/toolkit/devtools/debugger/test/addon-source/browser_dbg_addon5/testxul2.js
new file mode 100644
index 000000000..7ac4eabc7
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/addon1.xpi b/toolkit/devtools/debugger/test/addon1.xpi
new file mode 100644
index 000000000..b77ec9531
--- /dev/null
+++ b/toolkit/devtools/debugger/test/addon1.xpi
Binary files differ
diff --git a/toolkit/devtools/debugger/test/addon2.xpi b/toolkit/devtools/debugger/test/addon2.xpi
new file mode 100644
index 000000000..460eaca8a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/addon2.xpi
Binary files differ
diff --git a/toolkit/devtools/debugger/test/addon3.xpi b/toolkit/devtools/debugger/test/addon3.xpi
new file mode 100644
index 000000000..673b31b9d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/addon3.xpi
Binary files differ
diff --git a/toolkit/devtools/debugger/test/addon4.xpi b/toolkit/devtools/debugger/test/addon4.xpi
new file mode 100644
index 000000000..56dc98f6e
--- /dev/null
+++ b/toolkit/devtools/debugger/test/addon4.xpi
Binary files differ
diff --git a/toolkit/devtools/debugger/test/addon5.xpi b/toolkit/devtools/debugger/test/addon5.xpi
new file mode 100644
index 000000000..16991f7a0
--- /dev/null
+++ b/toolkit/devtools/debugger/test/addon5.xpi
Binary files differ
diff --git a/toolkit/devtools/debugger/test/browser.ini b/toolkit/devtools/debugger/test/browser.ini
new file mode 100644
index 000000000..eeba980ae
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser.ini
@@ -0,0 +1,562 @@
+[DEFAULT]
+subsuite = devtools
+support-files =
+ addon1.xpi
+ addon2.xpi
+ addon3.xpi
+ addon4.xpi
+ addon5.xpi
+ 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_breakpoints-break-on-last-line-of-script-on-reload.js
+ code_breakpoints-other-tabs.js
+ code_frame-script.js
+ code_function-search-01.js
+ code_function-search-02.js
+ code_function-search-03.js
+ code_location-changes.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_tracing-01.js
+ 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^
+ doc_auto-pretty-print-01.html
+ doc_auto-pretty-print-02.html
+ doc_binary_search.html
+ doc_blackboxing.html
+ doc_breakpoints-break-on-last-line-of-script-on-reload.html
+ doc_breakpoints-other-tabs.html
+ doc_breakpoints-reload.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_frame-parameters.html
+ doc_function-display-name.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_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.html
+ doc_random-javascript.html
+ doc_recursion-stack.html
+ doc_same-line-functions.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_split-console-paused-reload.html
+ doc_step-out.html
+ doc_terminate-on-tab-close.html
+ doc_tracing-01.html
+ doc_watch-expressions.html
+ doc_watch-expression-button.html
+ doc_with-frame.html
+ head.js
+ sjs_random-javascript.sjs
+ testactors.js
+
+[browser_dbg_aaa_run_first_leaktest.js]
+skip-if = e10s && debug
+[browser_dbg_addonactor.js]
+skip-if = e10s && debug
+[browser_dbg_addon-sources.js]
+skip-if = e10s && debug
+[browser_dbg_addon-modules.js]
+skip-if = e10s # TODO
+[browser_dbg_addon-modules-unpacked.js]
+skip-if = e10s # TODO
+[browser_dbg_addon-panels.js]
+skip-if = e10s && debug
+[browser_dbg_addon-console.js]
+skip-if = e10s && debug || os == 'win' # bug 1005274
+[browser_dbg_auto-pretty-print-01.js]
+skip-if = e10s && debug
+[browser_dbg_auto-pretty-print-02.js]
+skip-if = e10s && debug
+[browser_dbg_bfcache.js]
+skip-if = e10s || true # bug 1113935
+[browser_dbg_blackboxing-01.js]
+skip-if = e10s && debug
+[browser_dbg_blackboxing-02.js]
+skip-if = e10s && debug
+[browser_dbg_blackboxing-03.js]
+skip-if = e10s && debug
+[browser_dbg_blackboxing-04.js]
+skip-if = e10s && debug
+[browser_dbg_blackboxing-05.js]
+skip-if = e10s && debug
+[browser_dbg_blackboxing-06.js]
+skip-if = e10s && debug
+[browser_dbg_breadcrumbs-access.js]
+skip-if = e10s && debug
+[browser_dbg_break-on-dom-01.js]
+skip-if = e10s && debug
+[browser_dbg_break-on-dom-02.js]
+skip-if = e10s && debug
+[browser_dbg_break-on-dom-03.js]
+skip-if = e10s && debug
+[browser_dbg_break-on-dom-04.js]
+skip-if = e10s && debug
+[browser_dbg_break-on-dom-05.js]
+skip-if = e10s && debug
+[browser_dbg_break-on-dom-06.js]
+skip-if = e10s && debug
+[browser_dbg_break-on-dom-07.js]
+skip-if = e10s && debug
+[browser_dbg_break-on-dom-08.js]
+skip-if = e10s && debug
+[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_breakpoints-actual-location.js]
+skip-if = e10s && debug
+[browser_dbg_breakpoints-actual-location2.js]
+skip-if = e10s && debug
+[browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js]
+skip-if = e10s # Bug 1093535
+[browser_dbg_breakpoints-button-01.js]
+skip-if = e10s && debug
+[browser_dbg_breakpoints-button-02.js]
+skip-if = e10s && debug
+[browser_dbg_breakpoints-contextmenu-add.js]
+skip-if = e10s && debug
+[browser_dbg_breakpoints-contextmenu.js]
+skip-if = e10s && debug
+[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_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_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_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_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_hit-counts-01.js]
+skip-if = e10s && debug
+[browser_dbg_hit-counts-02.js]
+skip-if = e10s && debug
+[browser_dbg_host-layout.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_interrupts.js]
+skip-if = e10s && debug
+[browser_dbg_listaddons.js]
+skip-if = e10s && debug
+[browser_dbg_listtabs-01.js]
+skip-if = e10s # TODO
+[browser_dbg_listtabs-02.js]
+skip-if = e10s # TODO
+[browser_dbg_listtabs-03.js]
+skip-if = e10s && debug
+[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
+[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_pause-exceptions-01.js]
+skip-if = e10s && debug
+[browser_dbg_pause-exceptions-02.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_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 = e10a && debug
+[browser_dbg_reload-preferred-script-01.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
+[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 = e10s && debug
+[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]
+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]
+skip-if = e10s && debug
+[browser_dbg_sources-labels.js]
+skip-if = e10s && debug
+[browser_dbg_sources-sorting.js]
+skip-if = e10s && debug
+[browser_dbg_sources-bookmarklet.js]
+skip-if = e10s && debug
+[browser_dbg_split-console-paused-reload.js]
+skip-if = 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
+[browser_dbg_stack-06.js]
+skip-if = e10s && debug
+[browser_dbg_stack-07.js]
+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_tracing-01.js]
+skip-if = e10s && debug
+[browser_dbg_tracing-02.js]
+skip-if = e10s && debug
+[browser_dbg_tracing-03.js]
+skip-if = e10s && debug
+[browser_dbg_tracing-04.js]
+skip-if = e10s && debug
+[browser_dbg_tracing-05.js]
+skip-if = e10s && debug
+[browser_dbg_tracing-06.js]
+skip-if = e10s && debug
+[browser_dbg_tracing-07.js]
+skip-if = e10s && debug
+[browser_dbg_tracing-08.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-accessibility.js]
+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 || buildapp == 'mulet'
+[browser_dbg_variables-view-hide-non-enums.js]
+skip-if = e10s && debug
+[browser_dbg_variables-view-large-array-buffer.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-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
diff --git a/toolkit/devtools/debugger/test/browser_dbg_aaa_run_first_leaktest.js b/toolkit/devtools/debugger/test/browser_dbg_aaa_run_first_leaktest.js
new file mode 100644
index 000000000..720dbeba5
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_aaa_run_first_leaktest.js
@@ -0,0 +1,27 @@
+/* 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);
+
+ initDebugger(TAB_URL).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/toolkit/devtools/debugger/test/browser_dbg_addon-console.js b/toolkit/devtools/debugger/test/browser_dbg_addon-console.js
new file mode 100644
index 000000000..3539e5e62
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_addon-console.js
@@ -0,0 +1,44 @@
+/* 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_URL = EXAMPLE_URL + "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 addAddon(ADDON_URL);
+ let addonDebugger = yield initAddonDebugger(ADDON_URL);
+
+ 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/toolkit/devtools/debugger/test/browser_dbg_addon-modules-unpacked.js b/toolkit/devtools/debugger/test/browser_dbg_addon-modules-unpacked.js
new file mode 100644
index 000000000..382f56c4a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_addon-modules-unpacked.js
@@ -0,0 +1,61 @@
+/* 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_URL = EXAMPLE_URL + "addon5.xpi";
+
+function test() {
+ Task.spawn(function () {
+ let addon = yield addAddon(ADDON_URL);
+ let tab1 = yield addTab("chrome://browser_dbg_addon5/content/test.xul");
+
+ let addonDebugger = yield initAddonDebugger(ADDON_URL);
+
+ is(addonDebugger.title, "Debugger - Test unpacked add-on with JS Modules", "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@tests.mozilla.org/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@tests.mozilla.org/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/toolkit/devtools/debugger/test/browser_dbg_addon-modules.js b/toolkit/devtools/debugger/test/browser_dbg_addon-modules.js
new file mode 100644
index 000000000..1f4ae393d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_addon-modules.js
@@ -0,0 +1,61 @@
+/* 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_URL = EXAMPLE_URL + "addon4.xpi";
+
+function test() {
+ Task.spawn(function () {
+ let addon = yield addAddon(ADDON_URL);
+ let tab1 = yield addTab("chrome://browser_dbg_addon4/content/test.xul");
+
+ let addonDebugger = yield initAddonDebugger(ADDON_URL);
+
+ is(addonDebugger.title, "Debugger - Test add-on with JS Modules", "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("/browser_dbg_addon4@tests.mozilla.org.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("/browser_dbg_addon4@tests.mozilla.org.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/toolkit/devtools/debugger/test/browser_dbg_addon-panels.js b/toolkit/devtools/debugger/test/browser_dbg_addon-panels.js
new file mode 100644
index 000000000..98d9fc60e
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_addon-panels.js
@@ -0,0 +1,51 @@
+/* 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_URL = EXAMPLE_URL + "addon3.xpi";
+
+let gAddon, gClient, gThreadClient, gDebugger, gSources;
+let PREFS = [
+ "devtools.canvasdebugger.enabled",
+ "devtools.shadereditor.enabled",
+ "devtools.profiler.enabled",
+ "devtools.netmonitor.enabled"
+];
+function test() {
+ Task.spawn(function () {
+ let addon = yield addAddon(ADDON_URL);
+ let addonDebugger = yield initAddonDebugger(ADDON_URL);
+
+ // Store and enable all optional dev tools panels
+ let originalPrefs = PREFS.map(pref => {
+ let original = Services.prefs.getBoolPref(pref);
+ Services.prefs.setBoolPref(pref, true)
+ return original;
+ });
+
+ // 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);
+
+ PREFS.forEach((pref, i) => Services.prefs.setBoolPref(pref, originalPrefs[i]));
+
+ finish();
+ });
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_addon-sources.js b/toolkit/devtools/debugger/test/browser_dbg_addon-sources.js
new file mode 100644
index 000000000..89b4ebcbd
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_addon-sources.js
@@ -0,0 +1,36 @@
+/* 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_URL = EXAMPLE_URL + "addon3.xpi";
+let gClient;
+
+function test() {
+ Task.spawn(function () {
+ let addon = yield addAddon(ADDON_URL);
+ let addonDebugger = yield initAddonDebugger(ADDON_URL);
+
+ is(addonDebugger.title, "Debugger - browser_dbg_addon3", "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("/jid1-ami3akps3baaeg@jetpack.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/toolkit/devtools/debugger/test/browser_dbg_addonactor.js b/toolkit/devtools/debugger/test/browser_dbg_addonactor.js
new file mode 100644
index 000000000..9c511ceb8
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_addonactor.js
@@ -0,0 +1,98 @@
+/* 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_URL = EXAMPLE_URL + "addon3.xpi";
+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((aType, aTraits) => {
+ is(aType, "browser",
+ "Root actor should identify itself as a browser.");
+
+ installAddon()
+ .then(attachAddonActorForUrl.bind(null, gClient, ADDON3_URL))
+ .then(attachAddonThread)
+ .then(testDebugger)
+ .then(testSources)
+ .then(closeConnection)
+ .then(uninstallAddon)
+ .then(finish)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function installAddon () {
+ return addAddon(ADDON3_URL).then(aAddon => {
+ gAddon = aAddon;
+ });
+}
+
+function attachAddonThread ([aGrip, aResponse]) {
+ info("attached addon actor for URL");
+ 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.contains(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);
+}
+
+function closeConnection () {
+ let deferred = promise.defer();
+ gClient.close(deferred.resolve);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gClient = null;
+ gAddon = null;
+ gThreadClient = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_auto-pretty-print-01.js b/toolkit/devtools/debugger/test/browser_dbg_auto-pretty-print-01.js
new file mode 100644
index 000000000..2ed04bd11
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_auto-pretty-print-01.js
@@ -0,0 +1,110 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gPrefs, gOptions, gView;
+
+let gFirstSourceLabel = "code_ugly-5.js";
+let gSecondSourceLabel = "code_ugly-6.js";
+
+let gOriginalPref = Services.prefs.getBoolPref("devtools.debugger.auto-pretty-print");
+Services.prefs.setBoolPref("devtools.debugger.auto-pretty-print", true);
+
+function test(){
+ initDebugger(TAB_URL).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;
+
+ // Should be on by default.
+ testAutoPrettyPrintOn();
+
+ waitForSourceShown(gPanel, gFirstSourceLabel)
+ .then(testSourceIsUgly)
+ .then(() => waitForSourceShown(gPanel, gFirstSourceLabel))
+ .then(testSourceIsPretty)
+ .then(disableAutoPrettyPrint)
+ .then(testAutoPrettyPrintOff)
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ gSources.selectedIndex = 1;
+ return finished;
+ })
+ .then(testSecondSourceLabel)
+ .then(testSourceIsUgly)
+ // Re-enable auto pretty printing for browser_dbg_auto-pretty-print-02.js
+ .then(enableAutoPrettyPrint)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
+ })
+ });
+}
+
+function testSourceIsUgly() {
+ ok(!gEditor.getText().contains("\n "),
+ "The source shouldn't be pretty printed yet.");
+}
+
+function testSecondSourceLabel(){
+ let source = gSources.selectedItem.attachment.source;
+ ok(source.url === EXAMPLE_URL + gSecondSourceLabel,
+ "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().contains("\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/toolkit/devtools/debugger/test/browser_dbg_auto-pretty-print-02.js b/toolkit/devtools/debugger/test/browser_dbg_auto-pretty-print-02.js
new file mode 100644
index 000000000..65261a040
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_auto-pretty-print-02.js
@@ -0,0 +1,119 @@
+/* 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";
+
+let gTab, gDebuggee, gPanel, gDebugger;
+let gEditor, gSources, gPrefs, gOptions, gView;
+
+let gFirstSourceLabel = "code_ugly-6.js";
+let gSecondSourceLabel = "code_ugly-7.js";
+
+let gOriginalPref = Services.prefs.getBoolPref("devtools.debugger.auto-pretty-print");
+Services.prefs.setBoolPref("devtools.debugger.auto-pretty-print", true);
+
+function test(){
+ initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+ gTab = aTab;
+ gDebuggee = aDebuggee;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gPrefs = gDebugger.Prefs;
+ gOptions = gDebugger.DebuggerView.Options;
+ gView = gDebugger.DebuggerView;
+
+ // Should be on by default.
+ testAutoPrettyPrintOn();
+
+ waitForSourceShown(gPanel, gFirstSourceLabel)
+ .then(testSourceIsUgly)
+ .then(() => waitForSourceShown(gPanel, gFirstSourceLabel))
+ .then(testSourceIsPretty)
+ .then(testPrettyPrintButtonOn)
+ .then(() => {
+ // Switch to the second source.
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ gSources.selectedIndex = 1;
+ return finished;
+ })
+ .then(testSecondSourceLabel)
+ .then(() => {
+ // Switch back to first source.
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ gSources.selectedIndex = 0;
+ return finished;
+ })
+ .then(testFirstSourceLabel)
+ .then(testPrettyPrintButtonOn)
+ // Disable auto pretty printing so it does not affect the following tests.
+ .then(disableAutoPrettyPrint)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
+ })
+ });
+}
+
+function testSourceIsUgly() {
+ ok(!gEditor.getText().contains("\n "),
+ "The source shouldn't be pretty printed yet.");
+}
+
+function testFirstSourceLabel(){
+ let source = gSources.selectedItem.attachment.source;
+ ok(source.url === EXAMPLE_URL + gFirstSourceLabel,
+ "First source url is correct.");
+}
+
+function testSecondSourceLabel(){
+ let source = gSources.selectedItem.attachment.source;
+ ok(source.url === EXAMPLE_URL + gSecondSourceLabel,
+ "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.");
+ // Wait for the pref update to be communicated to the server.
+ return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+}
+
+function testSourceIsPretty() {
+ ok(gEditor.getText().contains("\n "),
+ "The source should be pretty printed.")
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gDebuggee = 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/toolkit/devtools/debugger/test/browser_dbg_bfcache.js b/toolkit/devtools/debugger/test/browser_dbg_bfcache.js
new file mode 100644
index 000000000..9af0d4989
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_bfcache.js
@@ -0,0 +1,89 @@
+/* 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";
+
+let gTab, gDebuggee, gPanel, gDebugger;
+let gSources;
+
+const test = Task.async(function* () {
+ info("Starting browser_dbg_bfcache.js's `test`.");
+
+ ([gTab, gDebuggee, gPanel]) = yield initDebugger(TAB_URL_1);
+ 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/toolkit/devtools/debugger/test/browser_dbg_blackboxing-01.js b/toolkit/devtools/debugger/test/browser_dbg_blackboxing-01.js
new file mode 100644
index 000000000..4bcb052a9
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_blackboxing-01.js
@@ -0,0 +1,52 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ waitForSourceShown(gPanel, ".coffee")
+ .then(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(aSource => {
+ ok(aSource.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/toolkit/devtools/debugger/test/browser_dbg_blackboxing-02.js b/toolkit/devtools/debugger/test/browser_dbg_blackboxing-02.js
new file mode 100644
index 000000000..4a66c7203
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_blackboxing-02.js
@@ -0,0 +1,55 @@
+/* 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"
+
+let gTab, gPanel, gDebugger;
+let gFrames;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+
+ waitForSourceShown(gPanel, BLACKBOXME_URL)
+ .then(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(aSource => {
+ ok(aSource.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/toolkit/devtools/debugger/test/browser_dbg_blackboxing-03.js b/toolkit/devtools/debugger/test/browser_dbg_blackboxing-03.js
new file mode 100644
index 000000000..374fb8f1f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_blackboxing-03.js
@@ -0,0 +1,59 @@
+/* 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"
+
+let gTab, gPanel, gDebugger;
+let gFrames, gSources;
+
+function test() {
+ initDebugger(TAB_URL).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/toolkit/devtools/debugger/test/browser_dbg_blackboxing-04.js b/toolkit/devtools/debugger/test/browser_dbg_blackboxing-04.js
new file mode 100644
index 000000000..4d3df406d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_blackboxing-04.js
@@ -0,0 +1,60 @@
+/* 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"
+
+let gTab, gPanel, gDebugger;
+let gFrames, gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceShown(gPanel, BLACKBOXME_URL)
+ .then(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/toolkit/devtools/debugger/test/browser_dbg_blackboxing-05.js b/toolkit/devtools/debugger/test/browser_dbg_blackboxing-05.js
new file mode 100644
index 000000000..86d13f76a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_blackboxing-05.js
@@ -0,0 +1,69 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gDeck;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gDeck = gDebugger.document.getElementById("editor-deck");
+
+ waitForSourceShown(gPanel, ".coffee")
+ .then(testSourceEditorShown)
+ .then(toggleBlackBoxing.bind(null, 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 waitForThreadEvents(gPanel, "blackboxchange");
+}
+
+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/toolkit/devtools/debugger/test/browser_dbg_blackboxing-06.js b/toolkit/devtools/debugger/test/browser_dbg_blackboxing-06.js
new file mode 100644
index 000000000..25fbd3ae3
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_blackboxing-06.js
@@ -0,0 +1,55 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 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/toolkit/devtools/debugger/test/browser_dbg_breadcrumbs-access.js b/toolkit/devtools/debugger/test/browser_dbg_breadcrumbs-access.js
new file mode 100644
index 000000000..40b8a5170
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breadcrumbs-access.js
@@ -0,0 +1,86 @@
+/* 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;
+
+ initDebugger(TAB_URL).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 });
+
+ 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),
+ waitForEditorLocationSet(gPanel),
+ EventUtils.sendKey("UP", gDebugger)
+ ]);
+ checkState({ frame: 0, source: 0, line: 5 });
+
+ yield promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ waitForSourceAndCaret(gPanel, "-02.js", 6),
+ waitForEditorLocationSet(gPanel),
+ EventUtils.sendKey("END", gDebugger)
+ ]);
+ checkState({ frame: 1, source: 1, line: 6 });
+
+ yield promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ waitForSourceAndCaret(gPanel, "-01.js", 5),
+ waitForEditorLocationSet(gPanel),
+ 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/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-01.js b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-01.js
new file mode 100644
index 000000000..86be67b00
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-01.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * 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() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gEvents = gView.EventListeners;
+
+ 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() {
+ yield waitForSourceShown(aPanel, ".html");
+ is(gEvents.itemCount, 0, "There should be no events before reloading.");
+
+ let reloaded = waitForSourcesAfterReload();
+ 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);
+ });
+
+ function waitForSourcesAfterReload() {
+ return promise.all([
+ waitForDebuggerEvents(aPanel, gDebugger.EVENTS.NEW_SOURCE),
+ waitForDebuggerEvents(aPanel, gDebugger.EVENTS.SOURCES_ADDED),
+ waitForDebuggerEvents(aPanel, gDebugger.EVENTS.SOURCE_SHOWN)
+ ]);
+ }
+ });
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-02.js b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-02.js
new file mode 100644
index 000000000..3a5d127c9
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-02.js
@@ -0,0 +1,126 @@
+/* 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() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gEvents = gView.EventListeners;
+
+ Task.spawn(function() {
+ yield waitForSourceShown(aPanel, ".html");
+ yield testFetchOnFocus();
+ yield testFetchOnReloadWhenFocused();
+ yield testFetchOnReloadWhenNotFocused();
+ yield closeDebuggerAndFinish(aPanel);
+ });
+
+ function testFetchOnFocus() {
+ return Task.spawn(function() {
+ let fetched = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_LISTENERS_FETCHED);
+
+ 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 = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_LISTENERS_FETCHED);
+
+ let reloading = once(gDebugger.gTarget, "will-navigate");
+ let reloaded = waitForSourcesAfterReload();
+ 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() {
+ 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 }, 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 = waitForSourcesAfterReload();
+ 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.");
+ });
+ }
+
+ function waitForSourcesAfterReload() {
+ return promise.all([
+ waitForDebuggerEvents(aPanel, gDebugger.EVENTS.NEW_SOURCE),
+ waitForDebuggerEvents(aPanel, gDebugger.EVENTS.SOURCES_ADDED),
+ waitForDebuggerEvents(aPanel, gDebugger.EVENTS.SOURCE_SHOWN)
+ ]);
+ }
+ });
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-03.js b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-03.js
new file mode 100644
index 000000000..5ce03e561
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-03.js
@@ -0,0 +1,89 @@
+/* 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() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gEvents = gView.EventListeners;
+
+ Task.spawn(function() {
+ yield waitForSourceShown(aPanel, ".html");
+
+ let fetched = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_LISTENERS_FETCHED);
+ 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.");
+ is(gEvents.widget._parent.querySelectorAll(".side-menu-widget-group-checkbox").length, 3,
+ "There should be a checkbox for each group shown in the view.");
+
+ 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, "resumed");
+ yield closeDebuggerAndFinish(aPanel);
+ });
+
+ function testEventItem(index, label, type, selectors, checked) {
+ let item = gEvents.items[index];
+ let node = item.target;
+
+ ok(item.attachment.url.contains(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/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-04.js b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-04.js
new file mode 100644
index 000000000..e68c9b0c2
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-04.js
@@ -0,0 +1,97 @@
+/* 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() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gController = gDebugger.DebuggerController
+ let gEvents = gView.EventListeners;
+ let gBreakpoints = gController.Breakpoints;
+
+ Task.spawn(function() {
+ yield waitForSourceShown(aPanel, ".html");
+
+ let fetched = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_LISTENERS_FETCHED);
+ 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 = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
+ 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 = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
+ 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, "resumed");
+ 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(gBreakpoints.DOM.activeEventNames.toString(), checked,
+ "The correct event names are listed as being active breakpoints.");
+ }
+ });
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-05.js b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-05.js
new file mode 100644
index 000000000..5356e5b22
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-05.js
@@ -0,0 +1,124 @@
+/* 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() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gController = gDebugger.DebuggerController
+ let gEvents = gView.EventListeners;
+ let gBreakpoints = gController.Breakpoints;
+
+ Task.spawn(function() {
+ yield waitForSourceShown(aPanel, ".html");
+
+ let fetched = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_LISTENERS_FETCHED);
+ 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 = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
+ 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 = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
+ 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 = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
+ 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 = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
+ 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, "resumed");
+ 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(gBreakpoints.DOM.activeEventNames.toString(), checked,
+ "The correct event names are listed as being active breakpoints.");
+ }
+ });
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-06.js b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-06.js
new file mode 100644
index 000000000..38e5e3ee3
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-06.js
@@ -0,0 +1,123 @@
+/* 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() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gController = gDebugger.DebuggerController
+ let gEvents = gView.EventListeners;
+ let gBreakpoints = gController.Breakpoints;
+
+ Task.spawn(function() {
+ yield waitForSourceShown(aPanel, ".html");
+
+ let fetched = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_LISTENERS_FETCHED);
+ 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 = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
+ 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");
+
+ yield reloadActiveTab(aPanel, gDebugger.EVENTS.EVENT_LISTENERS_FETCHED);
+
+ 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 = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
+ 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", "");
+
+ yield reloadActiveTab(aPanel, gDebugger.EVENTS.EVENT_LISTENERS_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", "");
+
+ yield ensureThreadClientState(aPanel, "resumed");
+ 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(gBreakpoints.DOM.activeEventNames.toString(), checked,
+ "The correct event names are listed as being active breakpoints.");
+ }
+ });
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-07.js b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-07.js
new file mode 100644
index 000000000..1c5ce9034
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-07.js
@@ -0,0 +1,104 @@
+/* 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("about:blank").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/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-08.js b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-08.js
new file mode 100644
index 000000000..ce92a9ac1
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-08.js
@@ -0,0 +1,54 @@
+/* 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() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ let gTab = aTab;
+ let gDebugger = aPanel.panelWin;
+ let gView = gDebugger.DebuggerView;
+ let gEvents = gView.EventListeners;
+
+ Task.spawn(function() {
+ yield waitForSourceShown(aPanel, ".html");
+ yield callInTab(gTab, "addBodyClickEventListener");
+
+ let fetched = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_LISTENERS_FETCHED);
+ gView.toggleInstrumentsPane({ visible: true, animated: false }, 1);
+ yield fetched;
+ yield ensureThreadClientState(aPanel, "resumed");
+
+ is(gView.instrumentsPaneHidden, false,
+ "The instruments pane should be visible.");
+ is(gView.instrumentsPaneTab, "events-tab",
+ "The events tab should be selected.");
+
+ let updated = waitForDebuggerEvents(aPanel, gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED);
+ EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger);
+ yield updated;
+ yield ensureThreadClientState(aPanel, "resumed");
+
+ let paused = waitForCaretAndScopes(aPanel, 48);
+ sendMouseClickToTab(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/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-event-01.js b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-event-01.js
new file mode 100644
index 000000000..d8dfb8a08
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-event-01.js
@@ -0,0 +1,229 @@
+/* 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";
+
+let gClient, gThreadClient, gInput, gButton;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect((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(closeConnection)
+ .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 closeConnection() {
+ let deferred = promise.defer();
+ gClient.close(deferred.resolve);
+ 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/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-event-02.js b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-event-02.js
new file mode 100644
index 000000000..dd7a33a30
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_break-on-dom-event-02.js
@@ -0,0 +1,109 @@
+/* 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";
+
+let gClient, gThreadClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect((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(closeConnection)
+ .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);
+}
+
+function closeConnection() {
+ let deferred = promise.defer();
+ gClient.close(deferred.resolve);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gClient = null;
+ gThreadClient = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_breakpoints-actual-location.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-actual-location.js
new file mode 100644
index 000000000..cc9fcb72e
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-actual-location.js
@@ -0,0 +1,96 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gBreakpointsAdded = gBreakpoints._added;
+ gBreakpointsRemoving = gBreakpoints._removing;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest);
+ callInTab(gTab, "firstCall");
+ });
+
+ function performTest() {
+ is(gBreakpointsAdded.size, 0,
+ "No breakpoints currently added.");
+ is(gBreakpointsRemoving.size, 0,
+ "No breakpoints currently being removed.");
+ is(gEditor.getBreakpoints().length, 0,
+ "No breakpoints currently shown in the editor.");
+
+ gEditor.on("breakpointAdded", onEditorBreakpointAdd);
+ gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 4 }).then(onBreakpointAdd);
+ }
+
+ let onBpDebuggerAdd = false;
+ let onBpEditorAdd = false;
+
+ function onBreakpointAdd(aBreakpointClient) {
+ ok(aBreakpointClient,
+ "Breakpoint added, client received.");
+ is(aBreakpointClient.location.actor, gSources.selectedValue,
+ "Breakpoint client url is the same.");
+ is(aBreakpointClient.location.line, 6,
+ "Breakpoint client line is new.");
+
+ is(aBreakpointClient.requestedLocation.actor, gSources.selectedValue,
+ "Requested location url is correct");
+ is(aBreakpointClient.requestedLocation.line, 4,
+ "Requested location line is correct");
+
+ onBpDebuggerAdd = true;
+ maybeFinish();
+ }
+
+ function onEditorBreakpointAdd() {
+ gEditor.off("breakpointAdded", onEditorBreakpointAdd);
+
+ is(gEditor.getBreakpoints().length, 1,
+ "There is only one breakpoint in the editor");
+
+ ok(!gBreakpoints._getAdded({ actor: gSources.selectedValue, line: 4 }),
+ "There isn't any breakpoint added on an invalid line.");
+ ok(!gBreakpoints._getRemoving({ actor: gSources.selectedValue, line: 4 }),
+ "There isn't any breakpoint removed from an invalid line.");
+
+ ok(gBreakpoints._getAdded({ actor: gSources.selectedValue, line: 6 }),
+ "There is a breakpoint added on the actual line.");
+ ok(!gBreakpoints._getRemoving({ actor: gSources.selectedValue, line: 6 }),
+ "There isn't any breakpoint removed from the actual line.");
+
+ gBreakpoints._getAdded({ actor: gSources.selectedValue, line: 6 }).then(aBreakpointClient => {
+ is(aBreakpointClient.location.actor, gSources.selectedValue,
+ "Breakpoint client location actor is correct.");
+ is(aBreakpointClient.location.line, 6,
+ "Breakpoint client location line is correct.");
+
+ onBpEditorAdd = true;
+ maybeFinish();
+ });
+ }
+
+ function maybeFinish() {
+ info("onBpDebuggerAdd: " + onBpDebuggerAdd);
+ info("onBpEditorAdd: " + onBpEditorAdd);
+
+ if (onBpDebuggerAdd && onBpEditorAdd) {
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ }
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_breakpoints-actual-location2.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-actual-location2.js
new file mode 100644
index 000000000..e44335792
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-actual-location2.js
@@ -0,0 +1,102 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gBreakpointsAdded = gBreakpoints._added;
+ gBreakpointsRemoving = gBreakpoints._removing;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 1).then(performTest);
+ callInTab(gTab, "ermahgerd");
+ });
+
+ function performTest() {
+ is(gBreakpointsAdded.size, 0,
+ "No breakpoints currently added.");
+ is(gBreakpointsRemoving.size, 0,
+ "No breakpoints currently being removed.");
+ is(gEditor.getBreakpoints().length, 0,
+ "No breakpoints currently shown in the editor.");
+
+ Task.spawn(function*() {
+ let bpClient = yield gPanel.addBreakpoint({
+ actor: gSources.selectedValue,
+ line: 19
+ });
+ yield gPanel.addBreakpoint({
+ actor: gSources.selectedValue,
+ line: 20
+ });
+
+ let movedBpClient = yield gPanel.addBreakpoint({
+ actor: gSources.selectedValue,
+ line: 17
+ });
+ testMovedLocation(movedBpClient);
+
+ yield resumeAndTestBreakpoint(19);
+
+ yield gPanel.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);
+ });
+ }
+
+ 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 selectedBreakpoint = gSources._selectedBreakpointItem;
+ ok(selectedBreakpoint,
+ "There should be a selected breakpoint on line " + line);
+ is(selectedBreakpoint.attachment.line, line,
+ "The breakpoint on line " + line + " was not hit");
+ }
+
+ function testMovedLocation(breakpointClient) {
+ ok(breakpointClient,
+ "Breakpoint added, client received.");
+ is(breakpointClient.location.actor, gSources.selectedValue,
+ "Breakpoint client url is the same.");
+ is(breakpointClient.location.line, 19,
+ "Breakpoint client line is new.");
+
+ is(breakpointClient.requestedLocation.actor, gSources.selectedValue,
+ "Requested location url is correct");
+ is(breakpointClient.requestedLocation.line, 17,
+ "Requested location line is correct");
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
new file mode 100644
index 000000000..b069e944c
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
@@ -0,0 +1,108 @@
+/* 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;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gThreadClient = gDebugger.gThreadClient;
+ gEvents = gDebugger.EVENTS;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ 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 [bp1, bp2, bp3] = yield promise.all([
+ setBreakpoint({
+ url: CODE_URL,
+ line: 3
+ }),
+ setBreakpoint({
+ url: CODE_URL,
+ line: 4
+ }),
+ setBreakpoint({
+ url: CODE_URL,
+ 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([
+ rdpInvoke(bp1, bp1.remove),
+ rdpInvoke(bp2, bp1.remove),
+ rdpInvoke(bp3, bp1.remove),
+ ]);
+
+ 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/toolkit/devtools/debugger/test/browser_dbg_breakpoints-button-01.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-button-01.js
new file mode 100644
index 000000000..40d787a8a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-button-01.js
@@ -0,0 +1,60 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+
+ waitForSourceShown(gPanel, "-01.js")
+ .then(addBreakpoints)
+ .then(testDisableBreakpoints)
+ .then(testEnableBreakpoints)
+ .then(() => ensureThreadClientState(gPanel, "resumed"))
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ 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 testDisableBreakpoints() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED, 3);
+ gSources.toggleBreakpoints();
+ return finished.then(() => checkBreakpointsDisabled(true));
+ }
+
+ function testEnableBreakpoints() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED, 3);
+ gSources.toggleBreakpoints();
+ return finished.then(() => checkBreakpointsDisabled(false));
+ }
+
+ function checkBreakpointsDisabled(aState, aTotal = 3) {
+ let breakpoints = gSources.getAllBreakpoints();
+
+ is(breakpoints.length, aTotal,
+ "Breakpoints should still be set.");
+ is(breakpoints.filter(e => e.attachment.disabled == aState).length, aTotal,
+ "Breakpoints should be " + (aState ? "disabled" : "enabled") + ".");
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_breakpoints-button-02.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-button-02.js
new file mode 100644
index 000000000..9ae835396
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-button-02.js
@@ -0,0 +1,69 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+
+ waitForSourceShown(gPanel, "-01.js")
+ .then(addBreakpoints)
+ .then(disableSomeBreakpoints)
+ .then(testToggleBreakpoints)
+ .then(testEnableBreakpoints)
+ .then(() => ensureThreadClientState(gPanel, "resumed"))
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ 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 disableSomeBreakpoints() {
+ return promise.all([
+ gSources.disableBreakpoint({ actor: gSources.values[0], line: 5 }),
+ gSources.disableBreakpoint({ actor: gSources.values[1], line: 6 })
+ ]);
+ }
+
+ function testToggleBreakpoints() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED, 1);
+ gSources.toggleBreakpoints();
+ return finished.then(() => checkBreakpointsDisabled(true));
+ }
+
+ function testEnableBreakpoints() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED, 3);
+ gSources.toggleBreakpoints();
+ return finished.then(() => checkBreakpointsDisabled(false));
+ }
+
+ function checkBreakpointsDisabled(aState, aTotal = 3) {
+ let breakpoints = gSources.getAllBreakpoints();
+
+ is(breakpoints.length, aTotal,
+ "Breakpoints should still be set.");
+ is(breakpoints.filter(e => e.attachment.disabled == aState).length, aTotal,
+ "Breakpoints should be " + (aState ? "disabled" : "enabled") + ".");
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_breakpoints-contextmenu-add.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-contextmenu-add.js
new file mode 100644
index 000000000..36cdac034
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-contextmenu-add.js
@@ -0,0 +1,95 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gEditor, gSources, gContextMenu, gBreakpoints, gBreakpointsAdded;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gBreakpointsAdded = gBreakpoints._added;
+ gContextMenu = gDebugger.document.getElementById("sourceEditorContextMenu");
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+ .then(performTest)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ 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.");
+ 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 });
+
+ return testAddBreakpoint().then(testAddConditionalBreakpoint);
+ }
+
+ function testAddBreakpoint() {
+ gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
+ gEditor.emit("gutterClick", 6, 2);
+
+ return once(gContextMenu, "popupshown").then(() => {
+ is(gBreakpointsAdded.size, 0, "no breakpoints added");
+
+ let cmd = gContextMenu.querySelector('menuitem[command=addBreakpointCommand]');
+ let bpShown = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_SHOWN_IN_EDITOR);
+ EventUtils.synthesizeMouseAtCenter(cmd, {}, gDebugger);
+ return bpShown;
+ }).then(() => {
+ is(gBreakpointsAdded.size, 1,
+ "1 breakpoint correctly added");
+ is(gEditor.getBreakpoints().length, 1,
+ "1 breakpoint currently shown in the editor.");
+ ok(gBreakpoints._getAdded({ actor: gSources.values[1], line: 7 }),
+ "Breakpoint on line 7 exists");
+ });
+ }
+
+ function testAddConditionalBreakpoint() {
+ gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
+ gEditor.emit("gutterClick", 7, 2);
+
+ return once(gContextMenu, "popupshown").then(() => {
+ is(gBreakpointsAdded.size, 1,
+ "1 breakpoint correctly added");
+
+ let cmd = gContextMenu.querySelector('menuitem[command=addConditionalBreakpointCommand]');
+ let bpShown = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
+ EventUtils.synthesizeMouseAtCenter(cmd, {}, gDebugger);
+ return bpShown;
+ }).then(() => {
+ is(gBreakpointsAdded.size, 2,
+ "2 breakpoints correctly added");
+ is(gEditor.getBreakpoints().length, 2,
+ "2 breakpoints currently shown in the editor.");
+ ok(gBreakpoints._getAdded({ actor: gSources.values[1], line: 8 }),
+ "Breakpoint on line 8 exists");
+ });
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_breakpoints-contextmenu.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-contextmenu.js
new file mode 100644
index 000000000..2b8139dd3
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-contextmenu.js
@@ -0,0 +1,322 @@
+/* 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);
+
+ let gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+
+ waitForSourceShown(gPanel, "-01.js")
+ .then(performTestWhileNotPaused)
+ .then(performTestWhilePaused)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ 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(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 8 }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 9 }))
+ .then(() => ensureThreadClientState(gPanel, "resumed"));
+ }
+
+ function performTestWhileNotPaused() {
+ info("Performing test while not paused...");
+
+ return addBreakpoints()
+ .then(initialChecks)
+ .then(() => checkBreakpointToggleSelf(0))
+ .then(() => checkBreakpointToggleOthers(0))
+ .then(() => checkBreakpointToggleSelf(1))
+ .then(() => checkBreakpointToggleOthers(1))
+ .then(() => checkBreakpointToggleSelf(2))
+ .then(() => checkBreakpointToggleOthers(2))
+ .then(() => checkBreakpointToggleSelf(3))
+ .then(() => checkBreakpointToggleOthers(3))
+ .then(() => checkBreakpointToggleSelf(4))
+ .then(() => checkBreakpointToggleOthers(4))
+ .then(testDeleteAll);
+ }
+
+ function performTestWhilePaused() {
+ info("Performing test while paused...");
+
+ return addBreakpoints()
+ .then(initialChecks)
+ .then(pauseAndCheck)
+ .then(() => checkBreakpointToggleSelf(0))
+ .then(() => checkBreakpointToggleOthers(0))
+ .then(() => checkBreakpointToggleSelf(1))
+ .then(() => checkBreakpointToggleOthers(1))
+ .then(() => checkBreakpointToggleSelf(2))
+ .then(() => checkBreakpointToggleOthers(2))
+ .then(() => checkBreakpointToggleSelf(3))
+ .then(() => checkBreakpointToggleOthers(3))
+ .then(() => checkBreakpointToggleSelf(4))
+ .then(() => checkBreakpointToggleOthers(4))
+ .then(testDeleteAll);
+ }
+
+ function pauseAndCheck() {
+ let finished = waitForSourceAndCaretAndScopes(gPanel, "-01.js", 5).then(() => {
+ let source = gSources.selectedItem.attachment.source;
+ 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 source = gSources.selectedItem.attachment.source;
+ 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.");
+
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+
+ return finished;
+ }
+
+ function initialChecks() {
+ for (let source of gSources) {
+ for (let breakpoint of source) {
+ ok(gBreakpoints._getAdded(breakpoint.attachment),
+ "All breakpoint items should have corresponding promises (1).");
+ ok(!gBreakpoints._getRemoving(breakpoint.attachment),
+ "All breakpoint items should have corresponding promises (2).");
+ ok(breakpoint.attachment.actor,
+ "All breakpoint items should have corresponding promises (3).");
+ is(!!breakpoint.attachment.disabled, false,
+ "All breakpoints should initially be enabled.");
+
+ let prefix = "bp-cMenu-"; // "breakpoints context menu"
+ let identifier = gBreakpoints.getIdentifier(breakpoint.attachment);
+ 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 initially be hidden'.");
+ ok(!gDebugger.document.getElementById(disableSelfId).hasAttribute("hidden"),
+ "The 'Disable breakpoint' context menu item should initially not be hidden'.");
+ is(breakpoint.attachment.view.checkbox.getAttribute("checked"), "true",
+ "All breakpoints should initially have a checked checkbox.");
+ }
+ }
+ }
+
+ function checkBreakpointToggleSelf(aIndex) {
+ let deferred = promise.defer();
+
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelectorAll(".dbg-breakpoint")[aIndex],
+ gDebugger);
+
+ let selectedBreakpoint = gSources._selectedBreakpointItem;
+
+ ok(gBreakpoints._getAdded(selectedBreakpoint.attachment),
+ "There should be a breakpoint client available (1).");
+ ok(!gBreakpoints._getRemoving(selectedBreakpoint.attachment),
+ "There should be a breakpoint client available (2).");
+ ok(selectedBreakpoint.attachment.actor,
+ "There should be a breakpoint client available (3).");
+ is(!!selectedBreakpoint.attachment.disabled, false,
+ "The breakpoint should not be disabled yet (" + aIndex + ").");
+
+ gBreakpoints._getAdded(selectedBreakpoint.attachment).then(aBreakpointClient => {
+ ok(aBreakpointClient,
+ "There should be a breakpoint client available as a promise.");
+ });
+
+ let prefix = "bp-cMenu-"; // "breakpoints context menu"
+ let identifier = gBreakpoints.getIdentifier(selectedBreakpoint.attachment);
+ 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.attachment.line),
+ "The source editor caret position was incorrect (" + aIndex + ").");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED).then(() => {
+ ok(!gBreakpoints._getAdded(selectedBreakpoint.attachment),
+ "There should be no breakpoint client available (4).");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED).then(() => {
+ ok(gBreakpoints._getAdded(selectedBreakpoint.attachment),
+ "There should be a breakpoint client available (5).");
+
+ deferred.resolve();
+ });
+
+ // Test re-disabling this breakpoint.
+ gSources._onEnableSelf(selectedBreakpoint.attachment);
+ is(selectedBreakpoint.attachment.disabled, false,
+ "The current breakpoint should now 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(selectedBreakpoint.attachment.view.checkbox.hasAttribute("checked"),
+ "The breakpoint should now be checked.");
+ });
+
+ // Test disabling this breakpoint.
+ gSources._onDisableSelf(selectedBreakpoint.attachment);
+ is(selectedBreakpoint.attachment.disabled, true,
+ "The current breakpoint should now 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(!selectedBreakpoint.attachment.view.checkbox.hasAttribute("checked"),
+ "The breakpoint should now be unchecked.");
+
+ return deferred.promise;
+ }
+
+ function checkBreakpointToggleOthers(aIndex) {
+ let deferred = promise.defer();
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED, 4).then(() => {
+ let selectedBreakpoint = gSources._selectedBreakpointItem;
+
+ ok(gBreakpoints._getAdded(selectedBreakpoint.attachment),
+ "There should be a breakpoint client available (6).");
+ ok(!gBreakpoints._getRemoving(selectedBreakpoint.attachment),
+ "There should be a breakpoint client available (7).");
+ ok(selectedBreakpoint.attachment.actor,
+ "There should be a breakpoint client available (8).");
+ is(!!selectedBreakpoint.attachment.disabled, false,
+ "The targetted breakpoint should not have been disabled (" + aIndex + ").");
+
+ for (let source of gSources) {
+ for (let otherBreakpoint of source) {
+ if (otherBreakpoint != selectedBreakpoint) {
+ ok(!gBreakpoints._getAdded(otherBreakpoint.attachment),
+ "There should be no breakpoint client for a disabled breakpoint (9).");
+ is(otherBreakpoint.attachment.disabled, true,
+ "Non-targetted breakpoints should have been disabled (10).");
+ }
+ }
+ }
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED, 4).then(() => {
+ for (let source of gSources) {
+ for (let someBreakpoint of source) {
+ ok(gBreakpoints._getAdded(someBreakpoint.attachment),
+ "There should be a breakpoint client for all enabled breakpoints (11).");
+ is(someBreakpoint.attachment.disabled, false,
+ "All breakpoints should now have been enabled (12).");
+ }
+ }
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED, 5).then(() => {
+ for (let source of gSources) {
+ for (let someBreakpoint of source) {
+ ok(!gBreakpoints._getAdded(someBreakpoint.attachment),
+ "There should be no breakpoint client for a disabled breakpoint (13).");
+ is(someBreakpoint.attachment.disabled, true,
+ "All breakpoints should now have been disabled (14).");
+ }
+ }
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED, 5).then(() => {
+ for (let source of gSources) {
+ for (let someBreakpoint of source) {
+ ok(gBreakpoints._getAdded(someBreakpoint.attachment),
+ "There should be a breakpoint client for all enabled breakpoints (15).");
+ is(someBreakpoint.attachment.disabled, false,
+ "All breakpoints should now have been enabled (16).");
+ }
+ }
+
+ // Done.
+ deferred.resolve();
+ });
+
+ // Test re-enabling all breakpoints.
+ enableAll();
+ });
+
+ // Test disabling all breakpoints.
+ disableAll();
+ });
+
+ // Test re-enabling other breakpoints.
+ enableOthers();
+ });
+
+ // Test disabling other breakpoints.
+ disableOthers();
+
+ return deferred.promise;
+ }
+
+ function testDeleteAll() {
+ let deferred = promise.defer();
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED, 5).then(() => {
+ ok(!gSources._selectedBreakpointItem,
+ "There should be no breakpoint available after removing all breakpoints.");
+
+ for (let source of gSources) {
+ for (let otherBreakpoint of source) {
+ ok(false, "It's a trap!");
+ }
+ }
+
+ // Done.
+ deferred.resolve()
+ });
+
+ // Test deleting all breakpoints.
+ deleteAll();
+
+ return deferred.promise;
+ }
+
+ function disableOthers() {
+ gSources._onDisableOthers(gSources._selectedBreakpointItem.attachment);
+ }
+ function enableOthers() {
+ gSources._onEnableOthers(gSources._selectedBreakpointItem.attachment);
+ }
+ function disableAll() {
+ gSources._onDisableAll();
+ }
+ function enableAll() {
+ gSources._onEnableAll();
+ }
+ function deleteAll() {
+ gSources._onDeleteAll();
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_breakpoints-disabled-reload.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-disabled-reload.js
new file mode 100644
index 000000000..f9e491475
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-disabled-reload.js
@@ -0,0 +1,118 @@
+/* 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() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ let gTab = aTab;
+ let gDebugger = aPanel.panelWin;
+ let gEvents = gDebugger.EVENTS;
+ let gEditor = gDebugger.DebuggerView.editor;
+ let gSources = gDebugger.DebuggerView.Sources;
+ let gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ let gBreakpointLocation;
+ Task.spawn(function() {
+ yield waitForSourceShown(aPanel, "-01.js");
+ gBreakpointLocation = { actor: getSourceActor(gSources, EXAMPLE_URL + "code_script-switching-01.js"),
+ line: 5 };
+
+ yield aPanel.addBreakpoint(gBreakpointLocation);
+
+ yield ensureThreadClientState(aPanel, "resumed");
+ yield testWhenBreakpointEnabledAndFirstSourceShown();
+
+ yield reloadActiveTab(aPanel, gEvents.SOURCE_SHOWN);
+ yield testWhenBreakpointEnabledAndSecondSourceShown();
+
+ yield gSources.disableBreakpoint(gBreakpointLocation);
+ yield reloadActiveTab(aPanel, gEvents.SOURCE_SHOWN);
+ yield testWhenBreakpointDisabledAndSecondSourceShown();
+
+ yield gSources.enableBreakpoint(gBreakpointLocation);
+ yield reloadActiveTab(aPanel, gEvents.SOURCE_SHOWN);
+ yield testWhenBreakpointEnabledAndSecondSourceShown();
+
+ yield resumeDebuggerThenCloseAndFinish(aPanel);
+ });
+
+ function verifyView({ disabled, visible }) {
+ 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 breakpointItem = gSources.getBreakpoint(gBreakpointLocation);
+ let visibleBreakpoints = gEditor.getBreakpoints();
+ is(!!breakpointItem.attachment.disabled, disabled,
+ "The selected brekapoint state was correct.");
+ is(breakpointItem.attachment.view.checkbox.hasAttribute("checked"), !disabled,
+ "The selected brekapoint's checkbox state was correct.");
+
+ // Source editor starts counting line and column numbers from 0.
+ let breakpointLine = breakpointItem.attachment.line - 1;
+ let matchedBreakpoints = visibleBreakpoints.filter(e => e.line == breakpointLine);
+ is(!!matchedBreakpoints.length, visible,
+ "The selected breakpoint's visibility in the editor 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(aPanel, "-01.js");
+ yield verifyView({ disabled: false, visible: true });
+
+ callInTab(gTab, "firstCall");
+ yield waitForDebuggerEvents(aPanel, gEvents.FETCHED_SCOPES);
+ yield ensureSourceIs(aPanel, "-01.js");
+ yield ensureCaretAt(aPanel, 5);
+ yield verifyView({ disabled: false, visible: true });
+
+ executeSoon(() => gDebugger.gThreadClient.resume());
+ yield waitForSourceAndCaretAndScopes(aPanel, "-02.js", 1);
+ yield verifyView({ disabled: false, visible: false });
+ });
+ }
+
+ function testWhenBreakpointEnabledAndSecondSourceShown() {
+ return Task.spawn(function() {
+ yield ensureSourceIs(aPanel, "-02.js", true);
+ yield verifyView({ disabled: false, visible: false });
+
+ callInTab(gTab, "firstCall");
+ yield waitForSourceAndCaretAndScopes(aPanel, "-01.js", 1);
+ yield verifyView({ disabled: false, visible: true });
+
+ executeSoon(() => gDebugger.gThreadClient.resume());
+ yield waitForSourceAndCaretAndScopes(aPanel, "-02.js", 1);
+ yield verifyView({ disabled: false, visible: false });
+ });
+ }
+
+ function testWhenBreakpointDisabledAndSecondSourceShown() {
+ return Task.spawn(function() {
+ yield ensureSourceIs(aPanel, "-02.js", true);
+ yield verifyView({ disabled: true, visible: false });
+
+ callInTab(gTab, "firstCall");
+ yield waitForDebuggerEvents(aPanel, gEvents.FETCHED_SCOPES);
+ yield ensureSourceIs(aPanel, "-02.js");
+ yield ensureCaretAt(aPanel, 6);
+ yield verifyView({ disabled: true, visible: false });
+
+ executeSoon(() => gDebugger.gThreadClient.resume());
+ yield waitForDebuggerEvents(aPanel, gEvents.AFTER_FRAMES_CLEARED);
+ yield ensureSourceIs(aPanel, "-02.js");
+ yield ensureCaretAt(aPanel, 6);
+ yield verifyView({ disabled: true, visible: false });
+ });
+ }
+ });
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_breakpoints-editor.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-editor.js
new file mode 100644
index 000000000..c7174c8f8
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-editor.js
@@ -0,0 +1,301 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gBreakpointsAdded = gBreakpoints._added;
+ gBreakpointsRemoving = gBreakpoints._removing;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest);
+ 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(gBreakpointsAdded.size, 0,
+ "No breakpoints currently added.");
+ is(gBreakpointsRemoving.size, 0,
+ "No breakpoints currently being removed.");
+ is(gEditor.getBreakpoints().length, 0,
+ "No breakpoints currently shown in the editor.");
+
+ ok(!gBreakpoints._getAdded({ url: "foo", line: 3 }),
+ "_getAdded('foo', 3) returns falsey.");
+ ok(!gBreakpoints._getRemoving({ url: "bar", line: 3 }),
+ "_getRemoving('bar', 3) returns falsey.");
+
+ is(gSources.values[1], gSources.selectedValue,
+ "The second source should be currently selected.");
+
+ info("Add the first breakpoint.");
+ let location = { actor: gSources.selectedValue, line: 6 };
+ gEditor.once("breakpointAdded", onEditorBreakpointAddFirst);
+ gPanel.addBreakpoint(location).then(onBreakpointAddFirst);
+ }
+
+ 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 onBreakpointAddFirst(aBreakpointClient) {
+ breakpointsAdded++;
+
+ ok(aBreakpointClient,
+ "breakpoint1 added, client received.");
+ is(aBreakpointClient.location.actor, gSources.selectedValue,
+ "breakpoint1 client url is correct.");
+ is(aBreakpointClient.location.line, 6,
+ "breakpoint1 client line is correct.");
+
+ ok(gBreakpoints._getAdded(aBreakpointClient.location),
+ "breakpoint1 client found in the list of added breakpoints.");
+ ok(!gBreakpoints._getRemoving(aBreakpointClient.location),
+ "breakpoint1 client found in the list of removing breakpoints.");
+
+ is(gBreakpointsAdded.size, 1,
+ "The list of added breakpoints holds only one breakpoint.");
+ is(gBreakpointsRemoving.size, 0,
+ "The list of removing breakpoints holds no breakpoint.");
+
+ gBreakpoints._getAdded(aBreakpointClient.location).then(aClient => {
+ is(aClient, aBreakpointClient,
+ "_getAdded() returns the correct breakpoint.");
+ });
+
+ is(gSources.values[1], gSources.selectedValue,
+ "The second source should be currently selected.");
+
+ info("Remove the first breakpoint.");
+ gEditor.once("breakpointRemoved", onEditorBreakpointRemoveFirst);
+ gPanel.removeBreakpoint(aBreakpointClient.location).then(onBreakpointRemoveFirst);
+ }
+
+ 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 onBreakpointRemoveFirst(aLocation) {
+ breakpointsRemoved++;
+
+ ok(aLocation,
+ "breakpoint1 removed");
+ is(aLocation.actor, gSources.selectedValue,
+ "breakpoint1 removal url is correct.");
+ is(aLocation.line, 6,
+ "breakpoint1 removal line is correct.");
+
+ testBreakpointAddBackground();
+ }
+
+ function testBreakpointAddBackground() {
+ is(gBreakpointsAdded.size, 0,
+ "No breakpoints currently added.");
+ is(gBreakpointsRemoving.size, 0,
+ "No breakpoints currently being removed.");
+ is(gEditor.getBreakpoints().length, 0,
+ "No breakpoints currently shown in the editor.");
+
+ ok(!gBreakpoints._getAdded({ actor: gSources.selectedValue, line: 6 }),
+ "_getAdded('gSources.selectedValue', 6) returns falsey.");
+ ok(!gBreakpoints._getRemoving({ actor: gSources.selectedValue, line: 6 }),
+ "_getRemoving('gSources.selectedValue', 6) returns falsey.");
+
+ 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 };
+ let options = { noEditorUpdate: true };
+ gEditor.on("breakpointAdded", onEditorBreakpointAddBackgroundTrap);
+ gPanel.addBreakpoint(location, options).then(onBreakpointAddBackground);
+ }
+
+ 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 onBreakpointAddBackground(aBreakpointClient, aResponseError) {
+ breakpointsAdded++;
+
+ ok(aBreakpointClient,
+ "breakpoint2 added, client received");
+ is(aBreakpointClient.location.actor, gSources.values[0],
+ "breakpoint2 client url is correct.");
+ is(aBreakpointClient.location.line, 5,
+ "breakpoint2 client line is correct.");
+
+ ok(gBreakpoints._getAdded(aBreakpointClient.location),
+ "breakpoint2 client found in the list of added breakpoints.");
+ ok(!gBreakpoints._getRemoving(aBreakpointClient.location),
+ "breakpoint2 client found in the list of removing breakpoints.");
+
+ is(gBreakpointsAdded.size, 1,
+ "The list of added breakpoints holds only one breakpoint.");
+ is(gBreakpointsRemoving.size, 0,
+ "The list of removing breakpoints holds no breakpoint.");
+
+ gBreakpoints._getAdded(aBreakpointClient.location).then(aClient => {
+ is(aClient, aBreakpointClient,
+ "_getAdded() returns the correct breakpoint.");
+ });
+
+ is(gSources.values[1], gSources.selectedValue,
+ "The second source should be currently selected.");
+
+ // Remove the trap listener.
+ gEditor.off("breakpointAdded", onEditorBreakpointAddBackgroundTrap);
+
+ info("Switch to the first source, which is not yet selected");
+ gEditor.once("breakpointAdded", onEditorBreakpointAddSwitch);
+ gEditor.once("change", onEditorTextChanged);
+ gSources.selectedIndex = 0;
+ }
+
+ 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.");
+
+ let window = gEditor.container.contentWindow;
+ executeSoon(() => window.mozRequestAnimationFrame(onReadyForClick));
+ }
+
+ 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(gBreakpointsAdded.size, 0,
+ "No breakpoints currently added.");
+ is(gBreakpointsRemoving.size, 0,
+ "No breakpoints currently being removed.");
+ is(gEditor.getBreakpoints().length, 0,
+ "No breakpoints currently shown in the editor.");
+
+ ok(!gBreakpoints._getAdded({ actor: gSources.values[0], line: 5 }),
+ "_getAdded('gSources.values[0]', 5) returns falsey.");
+ ok(!gBreakpoints._getRemoving({ actor: gSources.values[0], line: 5 }),
+ "_getRemoving('gSources.values[0]', 5) returns falsey.");
+
+ ok(!gBreakpoints._getAdded({ actor: gSources.values[1], line: 6 }),
+ "_getAdded('gSources.values[1]', 6) returns falsey.");
+ ok(!gBreakpoints._getRemoving({ actor: gSources.values[1], line: 6 }),
+ "_getRemoving('gSources.values[1]', 6) returns falsey.");
+
+ ok(!gBreakpoints._getAdded({ actor: "foo", line: 3 }),
+ "_getAdded('foo', 3) returns falsey.");
+ ok(!gBreakpoints._getRemoving({ actor: "bar", line: 3 }),
+ "_getRemoving('bar', 3) returns falsey.");
+
+ 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/toolkit/devtools/debugger/test/browser_dbg_breakpoints-eval.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-eval.js
new file mode 100644
index 000000000..2fb63abb9
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-eval.js
@@ -0,0 +1,46 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+
+ waitForSourceShown(gPanel, "-eval.js")
+ .then(run)
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ function run() {
+ return Task.spawn(function*() {
+ let newSource = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.NEW_SOURCE);
+ callInTab(gTab, "evalSourceWithSourceURL");
+ yield newSource;
+
+ yield gPanel.addBreakpoint({ actor: gSources.values[0], line: 2 });
+ yield ensureThreadClientState(gPanel, "resumed");
+
+ const paused = waitForThreadEvents(gPanel, "paused");
+ callInTab(gTab, "bar");
+ let frame = (yield paused).frame;
+ is(frame.where.source.actor, gSources.values[0], "Should have broken on the eval'ed source");
+ is(frame.where.line, 2, "Should break on line 2");
+
+ yield resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_breakpoints-highlight.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-highlight.js
new file mode 100644
index 000000000..468cb0d90
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-highlight.js
@@ -0,0 +1,103 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gEditor, gSources;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceShown(gPanel, "-01.js")
+ .then(addBreakpoints)
+ .then(() => clickBreakpointAndCheck(0, 0, 5))
+ .then(() => clickBreakpointAndCheck(1, 1, 6))
+ .then(() => clickBreakpointAndCheck(2, 1, 7))
+ .then(() => clickBreakpointAndCheck(3, 1, 8))
+ .then(() => clickBreakpointAndCheck(4, 1, 9))
+ .then(() => ensureThreadClientState(gPanel, "resumed"))
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ function addBreakpoints() {
+ return promise.resolve(null)
+ .then(() => initialChecks(0, 1))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.values[0], line: 5 }))
+ .then(() => initialChecks(0, 5))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 6 }))
+ .then(() => waitForSourceShown(gPanel, "-02.js"))
+ .then(() => waitForCaretUpdated(gPanel, 6))
+ .then(() => initialChecks(1, 6))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 7 }))
+ .then(() => initialChecks(1, 7))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 8 }))
+ .then(() => initialChecks(1, 8))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 9 }))
+ .then(() => initialChecks(1, 9));
+ }
+
+ function initialChecks(aSourceIndex, aCaretLine) {
+ checkEditorContents(aSourceIndex);
+
+ is(gSources.selectedLabel, gSources.items[aSourceIndex].label,
+ "The currently selected source label is incorrect (0).");
+ is(gSources.selectedValue, gSources.items[aSourceIndex].value,
+ "The currently selected source value is incorrect (0).");
+ ok(isCaretPos(gPanel, aCaretLine),
+ "The editor caret line and column were incorrect (0).");
+ }
+
+ function clickBreakpointAndCheck(aBreakpointIndex, aSourceIndex, aCaretLine) {
+ let finished = waitForCaretUpdated(gPanel, aCaretLine).then(() => {
+ checkHighlight(gSources.values[aSourceIndex], aCaretLine);
+ checkEditorContents(aSourceIndex);
+
+ is(gSources.selectedLabel, gSources.items[aSourceIndex].label,
+ "The currently selected source label is incorrect (1).");
+ is(gSources.selectedValue, 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(aActor, aLine) {
+ is(gSources._selectedBreakpointItem, gSources.getBreakpoint({ actor: aActor, line: aLine }),
+ "The currently selected breakpoint item is incorrect.");
+ is(gSources._selectedBreakpointItem.attachment.actor, aActor,
+ "The selected breakpoint item's source location attachment is incorrect.");
+ is(gSources._selectedBreakpointItem.attachment.line, aLine,
+ "The selected breakpoint item's source line number is incorrect.");
+ ok(gSources._selectedBreakpointItem.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.");
+ }
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_breakpoints-new-script.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-new-script.js
new file mode 100644
index 000000000..b92472c65
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-new-script.js
@@ -0,0 +1,88 @@
+/* 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";
+
+let gTab, gPanel, gDebugger, gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ addBreakpoint();
+ });
+}
+
+function addBreakpoint() {
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 16).then(() => {
+ is(gDebugger.gThreadClient.state, "paused",
+ "The debugger statement was reached.");
+ ok(isCaretPos(gPanel, 16),
+ "The source editor caret position is incorrect (1).");
+
+ gPanel.addBreakpoint({ actor: getSourceActor(gSources, TAB_URL), line: 20 }).then(() => {
+ testResume();
+ });
+ });
+
+ callInTab(gTab, "runDebuggerStatement");
+}
+
+function testResume() {
+ 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).");
+
+ testBreakpointHit();
+ });
+ });
+
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+ });
+}
+
+function testBreakpointHit() {
+ 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).");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gSources = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_breakpoints-other-tabs.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-other-tabs.js
new file mode 100644
index 000000000..7aed9c502
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-other-tabs.js
@@ -0,0 +1,35 @@
+/* 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";
+
+let test = Task.async(function* () {
+ const [tab1,, panel1] = yield initDebugger(TAB_URL);
+ const [tab2,, panel2] = yield initDebugger(TAB_URL);
+
+ yield ensureSourceIs(panel1, "code_breakpoints-other-tabs.js", true);
+
+ const sources = panel1.panelWin.DebuggerView.Sources;
+
+ yield panel1.addBreakpoint({
+ actor: sources.selectedValue,
+ 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 teardown(panel1);
+ yield resumeDebuggerThenCloseAndFinish(panel2);
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_breakpoints-pane.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-pane.js
new file mode 100644
index 000000000..452f3cd2a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-pane.js
@@ -0,0 +1,254 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gBreakpointsAdded = gBreakpoints._added;
+ gBreakpointsRemoving = gBreakpoints._removing;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest);
+ callInTab(gTab, "firstCall");
+ });
+
+ let breakpointsAdded = 0;
+ let breakpointsDisabled = 0;
+ let breakpointsRemoved = 0;
+
+ 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(gBreakpointsAdded.size, 0,
+ "No breakpoints currently added.");
+ is(gBreakpointsRemoving.size, 0,
+ "No breakpoints currently being removed.");
+ is(gEditor.getBreakpoints().length, 0,
+ "No breakpoints currently shown in the editor.");
+
+ ok(!gBreakpoints._getAdded({ url: "foo", line: 3 }),
+ "_getAdded('foo', 3) returns falsey.");
+ ok(!gBreakpoints._getRemoving({ url: "bar", line: 3 }),
+ "_getRemoving('bar', 3) returns falsey.");
+
+ let breakpointsParent = gSources.widget._parent;
+ let 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.");
+
+ addBreakpoints(true).then(() => {
+ 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.");
+
+ disableBreakpoints().then(() => {
+ 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.");
+
+ addBreakpoints().then(() => {
+ 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.");
+
+ removeBreakpoints().then(() => {
+ 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.");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => {
+ finalCheck();
+ closeDebuggerAndFinish(gPanel);
+ });
+
+ gDebugger.gThreadClient.resume();
+ });
+ });
+ });
+ });
+
+ function addBreakpoints(aIncrementFlag) {
+ let deferred = promise.defer();
+
+ gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 6 }).then(aClient => {
+ onBreakpointAdd(aClient, {
+ increment: aIncrementFlag,
+ line: 6,
+ text: "debugger;"
+ });
+
+ gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 7 }).then(aClient => {
+ onBreakpointAdd(aClient, {
+ increment: aIncrementFlag,
+ line: 7,
+ text: "function foo() {}"
+ });
+
+ gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 9 }).then(aClient => {
+ onBreakpointAdd(aClient, {
+ increment: aIncrementFlag,
+ line: 9,
+ text: "foo();"
+ });
+
+ deferred.resolve();
+ });
+ });
+ });
+
+ return deferred.promise;
+ }
+
+ 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());
+
+ gSources.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());
+
+ gPanel.removeBreakpoint(breakpointItem.attachment).then(() => {
+ if (++breakpointsRemoved == breakpointsAdded) {
+ deferred.resolve();
+ }
+ });
+ }
+
+ return deferred.promise;
+ }
+
+ function onBreakpointAdd(aBreakpointClient, aTestData) {
+ if (aTestData.increment) {
+ breakpointsAdded++;
+ }
+
+ is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsAdded,
+ aTestData.increment
+ ? "Should have added a breakpoint in the pane."
+ : "Should have the same number of breakpoints in the pane.");
+
+ let identifier = gBreakpoints.getIdentifier(aBreakpointClient.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"), aTestData.line,
+ "The expected information wasn't found in the breakpoint element.");
+ is(text.getAttribute("value"), aTestData.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.");
+ }
+ }
+
+ function finalCheck() {
+ is(gBreakpointsAdded.size, 0,
+ "No breakpoints currently added.");
+ is(gBreakpointsRemoving.size, 0,
+ "No breakpoints currently being removed.");
+ is(gEditor.getBreakpoints().length, 0,
+ "No breakpoints currently shown in the editor.");
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_breakpoints-reload.js b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-reload.js
new file mode 100644
index 000000000..312ea389e
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_breakpoints-reload.js
@@ -0,0 +1,35 @@
+/* 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";
+
+let test = Task.async(function* () {
+ requestLongerTimeout(4);
+
+ const [tab,, panel] = yield initDebugger(TAB_URL);
+
+ yield ensureSourceIs(panel, "doc_breakpoints-reload.html", true);
+
+ const sources = panel.panelWin.DebuggerView.Sources;
+ yield panel.addBreakpoint({
+ actor: sources.selectedValue,
+ line: 10 // "break on me" string
+ });
+
+ const paused = waitForThreadEvents(panel, "paused");
+ reloadActiveTab(panel);
+ 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 waitForDebuggerEvents(panel, panel.panelWin.EVENTS.SOURCE_SHOWN)
+ yield resumeDebuggerThenCloseAndFinish(panel);
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_chrome-create.js b/toolkit/devtools/debugger/test/browser_dbg_chrome-create.js
new file mode 100644
index 000000000..185bd6f1b
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_chrome-create.js
@@ -0,0 +1,62 @@
+/* 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.
+ */
+
+let 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/toolkit/devtools/debugger/test/browser_dbg_chrome-debugging.js b/toolkit/devtools/debugger/test/browser_dbg_chrome-debugging.js
new file mode 100644
index 000000000..7043d3eeb
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_chrome-debugging.js
@@ -0,0 +1,100 @@
+/* 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";
+
+let gClient, gThreadClient;
+let gAttached = promise.defer();
+let gNewGlobal = promise.defer()
+let gNewChromeSource = promise.defer()
+
+let { DevToolsLoader } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let loader = new DevToolsLoader();
+loader.invisibleToDebugger = true;
+loader.main("devtools/server/main");
+let DebuggerServer = loader.DebuggerServer;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect((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.listTabs(aResponse => {
+ ok(aResponse.chromeDebugger.contains("chromeDebugger"),
+ "Chrome debugger actor should identify itself accordingly.");
+
+ gClient.addListener("newGlobal", onNewGlobal);
+ gClient.addListener("newSource", onNewSource);
+
+ gClient.attachThread(aResponse.chromeDebugger, (aResponse, aThreadClient) => {
+ gThreadClient = aThreadClient;
+
+ 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);
+
+ gClient.removeListener("newSource", onNewSource);
+ gNewChromeSource.resolve();
+ }
+}
+
+function resumeAndCloseConnection() {
+ let deferred = promise.defer();
+ gThreadClient.resume(() => gClient.close(deferred.resolve));
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gClient = null;
+ gThreadClient = null;
+ gAttached = null;
+ gNewGlobal = null;
+ gNewChromeSource = null;
+
+ loader = null;
+ DebuggerServer = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_clean-exit-window.js b/toolkit/devtools/debugger/test/browser_dbg_clean-exit-window.js
new file mode 100644
index 000000000..61b92cffa
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_clean-exit-window.js
@@ -0,0 +1,84 @@
+/* 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.
+ */
+
+let gDebuggee, gPanel, gDebugger, gWindow;
+
+const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html";
+
+function test() {
+ addWindow(TAB_URL)
+ .then(win => initDebugger(TAB_URL, 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/toolkit/devtools/debugger/test/browser_dbg_clean-exit.js b/toolkit/devtools/debugger/test/browser_dbg_clean-exit.js
new file mode 100644
index 000000000..4d41504ee
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_clean-exit.js
@@ -0,0 +1,38 @@
+/* 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.
+ */
+
+let gTab, gPanel, gDebugger;
+
+const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html";
+
+function test() {
+ initDebugger(TAB_URL).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/toolkit/devtools/debugger/test/browser_dbg_closure-inspection.js b/toolkit/devtools/debugger/test/browser_dbg_closure-inspection.js
new file mode 100644
index 000000000..47c99e57d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_closure-inspection.js
@@ -0,0 +1,148 @@
+/* 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;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ waitForSourceShown(gPanel, ".html")
+ .then(testClosure)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ function testClosure() {
+ sendMouseClickToTab(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/toolkit/devtools/debugger/test/browser_dbg_cmd-blackbox.js b/toolkit/devtools/debugger/test/browser_dbg_cmd-blackbox.js
new file mode 100644
index 000000000..797efc1a4
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_cmd-blackbox.js
@@ -0,0 +1,114 @@
+/* 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();
+
+ yield waitForDebuggerEvents(panel, panel.panelWin.EVENTS.SOURCE_SHOWN);
+
+ function cmd(aTyped, aEventRepeat = 1, aOutput = "") {
+ return promise.all([
+ waitForThreadEvents(panel, "blackboxchange", 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/toolkit/devtools/debugger/test/browser_dbg_cmd-break.js b/toolkit/devtools/debugger/test/browser_dbg_cmd-break.js
new file mode 100644
index 000000000..702c01059
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_cmd-break.js
@@ -0,0 +1,221 @@
+/* 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";
+let 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: function() {
+ return initDebugger(gBrowser.selectedTab).then(([aTab, aDebuggee, aPanel]) => {
+ // Spin the event loop before causing the debuggee to pause, to allow
+ // this function to return first.
+ executeSoon(() => aDebuggee.firstCall());
+
+ return waitForSourceAndCaretAndScopes(aPanel, ".html", 1).then(() => {
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gThreadClient = gPanel.panelWin.gThreadClient;
+ gLineNumber = '' + aOptions.window.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 \'breakpoint\'.'
+ }
+ }
+ }
+ }]);
+
+ 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/toolkit/devtools/debugger/test/browser_dbg_cmd-dbg.js b/toolkit/devtools/debugger/test/browser_dbg_cmd-dbg.js
new file mode 100644
index 000000000..7627acc4e
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_cmd-dbg.js
@@ -0,0 +1,100 @@
+/* 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/toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-01.js b/toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-01.js
new file mode 100644
index 000000000..952035efb
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-01.js
@@ -0,0 +1,243 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gBreakpointsAdded = gBreakpoints._added;
+ gBreakpointsRemoving = gBreakpoints._removing;
+
+ // This test forces conditional breakpoints to be evaluated on the
+ // client-side
+ var client = gPanel.target.client;
+ client.mainRoot.traits.conditionalBreakpoints = false;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
+ .then(() => addBreakpoints())
+ .then(() => initialChecks())
+ .then(() => resumeAndTestBreakpoint(20))
+ .then(() => resumeAndTestBreakpoint(21))
+ .then(() => resumeAndTestBreakpoint(22))
+ .then(() => resumeAndTestBreakpoint(23))
+ .then(() => resumeAndTestBreakpoint(24))
+ .then(() => resumeAndTestBreakpoint(25))
+ .then(() => resumeAndTestBreakpoint(27))
+ .then(() => resumeAndTestBreakpoint(28))
+ .then(() => {
+ // Note: the breakpoint on line 29 should not be hit since the
+ // conditional expression evaluates to undefined. It used to
+ // be on line 30, but it can't be the last breakpoint because
+ // there is a race condition (the "frames cleared" event might
+ // fire from the conditional expression evaluation if it's too
+ // slow, which is what we wait for to reload the page)
+ return resumeAndTestBreakpoint(30);
+ })
+ .then(() => resumeAndTestNoBreakpoint())
+ .then(() => {
+ return promise.all([
+ reloadActiveTab(gPanel, gDebugger.EVENTS.BREAKPOINT_SHOWN_IN_EDITOR, 13),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_SHOWN_IN_PANE, 13)
+ ]);
+ })
+ .then(() => testAfterReload())
+ .then(() => {
+ // Reset traits back to default value
+ client.mainRoot.traits.conditionalBreakpoints = true;
+ })
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "ermahgerd");
+ });
+
+ function addBreakpoints() {
+ return promise.resolve(null)
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 18 }))
+ .then(aClient => aClient.conditionalExpression = "undefined")
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 19 }))
+ .then(aClient => aClient.conditionalExpression = "null")
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 20 }))
+ .then(aClient => aClient.conditionalExpression = "42")
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 21 }))
+ .then(aClient => aClient.conditionalExpression = "true")
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 22 }))
+ .then(aClient => aClient.conditionalExpression = "'nasu'")
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 23 }))
+ .then(aClient => aClient.conditionalExpression = "/regexp/")
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 24 }))
+ .then(aClient => aClient.conditionalExpression = "({})")
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 25 }))
+ .then(aClient => aClient.conditionalExpression = "(function() {})")
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 26 }))
+ .then(aClient => aClient.conditionalExpression = "(function() { return false; })()")
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 27 }))
+ .then(aClient => aClient.conditionalExpression = "a")
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 28 }))
+ .then(aClient => aClient.conditionalExpression = "a !== undefined")
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 29 }))
+ .then(aClient => aClient.conditionalExpression = "b")
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 30 }))
+ .then(aClient => aClient.conditionalExpression = "a !== null");
+ }
+
+ function initialChecks() {
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ 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.");
+
+ is(gBreakpointsAdded.size, 13,
+ "13 breakpoints currently added.");
+ is(gBreakpointsRemoving.size, 0,
+ "No breakpoints currently being removed.");
+ is(gEditor.getBreakpoints().length, 13,
+ "13 breakpoints currently shown in the editor.");
+
+ ok(!gBreakpoints._getAdded({ url: "foo", line: 3 }),
+ "_getAdded('foo', 3) returns falsey.");
+ ok(!gBreakpoints._getRemoving({ url: "bar", line: 3 }),
+ "_getRemoving('bar', 3) returns falsey.");
+ }
+
+ function resumeAndTestBreakpoint(aLine) {
+ let finished = waitForCaretUpdated(gPanel, aLine).then(() => testBreakpoint(aLine));
+
+ 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._selectedBreakpointItem,
+ "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(aLine, aHighlightBreakpoint) {
+ // Highlight the breakpoint only if required.
+ if (aHighlightBreakpoint) {
+ let finished = waitForCaretUpdated(gPanel, aLine).then(() => testBreakpoint(aLine));
+ gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: aLine });
+ return finished;
+ }
+
+ let selectedActor = gSources.selectedValue;
+ let selectedBreakpoint = gSources._selectedBreakpointItem;
+
+ 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;
+
+ is(selectedBreakpoint.attachment.actor, source.actor,
+ "The breakpoint on line " + aLine + " wasn't added on the correct source.");
+ is(selectedBreakpoint.attachment.line, aLine,
+ "The breakpoint on line " + aLine + " wasn't found.");
+ is(!!selectedBreakpoint.attachment.disabled, false,
+ "The breakpoint on line " + aLine + " should be enabled.");
+ is(!!selectedBreakpoint.attachment.openPopup, false,
+ "The breakpoint on line " + aLine + " should not have opened a popup.");
+ is(gSources._conditionalPopupVisible, false,
+ "The breakpoint conditional expression popup should not have been shown.");
+
+ return gBreakpoints._getAdded(selectedBreakpoint.attachment).then(aBreakpointClient => {
+ is(aBreakpointClient.location.url, source.url,
+ "The breakpoint's client url is correct");
+ is(aBreakpointClient.location.line, aLine,
+ "The breakpoint's client line is correct");
+ isnot(aBreakpointClient.conditionalExpression, undefined,
+ "The breakpoint on line " + aLine + " should have a conditional expression.");
+
+ ok(isCaretPos(gPanel, aLine),
+ "The editor caret position is not properly set.");
+ });
+ }
+
+ function testAfterReload() {
+ let selectedActor = gSources.selectedValue;
+ let selectedBreakpoint = gSources._selectedBreakpointItem;
+
+ 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.");
+
+ return promise.resolve(null)
+ .then(() => testBreakpoint(18, true))
+ .then(() => testBreakpoint(19, true))
+ .then(() => testBreakpoint(20, true))
+ .then(() => testBreakpoint(21, true))
+ .then(() => testBreakpoint(22, true))
+ .then(() => testBreakpoint(23, true))
+ .then(() => testBreakpoint(24, true))
+ .then(() => testBreakpoint(25, true))
+ .then(() => testBreakpoint(26, true))
+ .then(() => testBreakpoint(27, true))
+ .then(() => testBreakpoint(28, true))
+ .then(() => testBreakpoint(29, true))
+ .then(() => testBreakpoint(30, true))
+ .then(() => {
+ 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._selectedBreakpointItem,
+ "There should be a selected breakpoint in the sources pane.")
+ is(gSources._conditionalPopupVisible, false,
+ "The breakpoint conditional expression popup should not be shown.");
+ });
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-02.js b/toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-02.js
new file mode 100644
index 000000000..2fff3b6dd
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-02.js
@@ -0,0 +1,202 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gBreakpointsAdded = gBreakpoints._added;
+ gBreakpointsRemoving = gBreakpoints._removing;
+
+ // This test forces conditional breakpoints to be evaluated on the
+ // client-side
+ var client = gPanel.target.client;
+ client.mainRoot.traits.conditionalBreakpoints = false;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
+ .then(() => initialChecks())
+ .then(() => addBreakpoint1())
+ .then(() => testBreakpoint(18, false, false, undefined))
+ .then(() => addBreakpoint2())
+ .then(() => testBreakpoint(19, false, false, undefined))
+ .then(() => modBreakpoint2())
+ .then(() => testBreakpoint(19, false, true, undefined))
+ .then(() => addBreakpoint3())
+ .then(() => testBreakpoint(20, true, false, undefined))
+ .then(() => modBreakpoint3())
+ .then(() => testBreakpoint(20, true, false, "bamboocha"))
+ .then(() => addBreakpoint4())
+ .then(() => testBreakpoint(21, false, false, undefined))
+ .then(() => delBreakpoint4())
+ .then(() => setCaretPosition(18))
+ .then(() => testBreakpoint(18, false, false, undefined))
+ .then(() => setCaretPosition(19))
+ .then(() => testBreakpoint(19, false, false, undefined))
+ .then(() => setCaretPosition(20))
+ .then(() => testBreakpoint(20, true, false, "bamboocha"))
+ .then(() => setCaretPosition(17))
+ .then(() => testNoBreakpoint(17))
+ .then(() => setCaretPosition(21))
+ .then(() => testNoBreakpoint(21))
+ .then(() => clickOnBreakpoint(0))
+ .then(() => testBreakpoint(18, false, false, undefined))
+ .then(() => clickOnBreakpoint(1))
+ .then(() => testBreakpoint(19, false, false, undefined))
+ .then(() => clickOnBreakpoint(2))
+ .then(() => testBreakpoint(20, true, true, "bamboocha"))
+ .then(() => {
+ // Reset traits back to default value
+ client.mainRoot.traits.conditionalBreakpoints = true;
+ })
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "ermahgerd");
+ });
+
+ function initialChecks() {
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ 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.");
+
+ is(gBreakpointsAdded.size, 0,
+ "No breakpoints currently added.");
+ is(gBreakpointsRemoving.size, 0,
+ "No breakpoints currently being removed.");
+ is(gEditor.getBreakpoints().length, 0,
+ "No breakpoints currently shown in the editor.");
+
+ ok(!gBreakpoints._getAdded({ actor: "foo", line: 3 }),
+ "_getAdded('foo', 3) returns falsey.");
+ ok(!gBreakpoints._getRemoving({ actor: "bar", line: 3 }),
+ "_getRemoving('bar', 3) returns falsey.");
+ }
+
+ function addBreakpoint1() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
+ gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 18 });
+ return finished;
+ }
+
+ function addBreakpoint2() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
+ setCaretPosition(19);
+ gSources._onCmdAddBreakpoint();
+ return finished;
+ }
+
+ function modBreakpoint2() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
+ setCaretPosition(19);
+ gSources._onCmdAddConditionalBreakpoint();
+ return finished;
+ }
+
+ function addBreakpoint3() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
+ setCaretPosition(20);
+ gSources._onCmdAddConditionalBreakpoint();
+ return finished;
+ }
+
+ function modBreakpoint3() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_HIDING);
+ typeText(gSources._cbTextbox, "bamboocha");
+ EventUtils.sendKey("RETURN", gDebugger);
+ return finished;
+ }
+
+ function addBreakpoint4() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
+ setCaretPosition(21);
+ gSources._onCmdAddBreakpoint();
+ return finished;
+ }
+
+ function delBreakpoint4() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
+ setCaretPosition(21);
+ gSources._onCmdAddBreakpoint();
+ return finished;
+ }
+
+ function testBreakpoint(aLine, aOpenPopupFlag, aPopupVisible, aConditionalExpression) {
+ let selectedActor = gSources.selectedValue;
+ let selectedBreakpoint = gSources._selectedBreakpointItem;
+
+ ok(selectedActor,
+ "There should be a selected item in the sources pane.");
+ ok(selectedBreakpoint,
+ "There should be a selected brekapoint in the sources pane.");
+
+ let source = gSources.selectedItem.attachment.source;
+
+ is(selectedBreakpoint.attachment.actor, source.actor,
+ "The breakpoint on line " + aLine + " wasn't added on the correct source.");
+ is(selectedBreakpoint.attachment.line, aLine,
+ "The breakpoint on line " + aLine + " wasn't found.");
+ is(!!selectedBreakpoint.attachment.disabled, false,
+ "The breakpoint on line " + aLine + " should be enabled.");
+ is(!!selectedBreakpoint.attachment.openPopup, aOpenPopupFlag,
+ "The breakpoint on line " + aLine + " should have a correct popup state (1).");
+ is(gSources._conditionalPopupVisible, aPopupVisible,
+ "The breakpoint on line " + aLine + " should have a correct popup state (2).");
+
+ return gBreakpoints._getAdded(selectedBreakpoint.attachment).then(aBreakpointClient => {
+ is(aBreakpointClient.location.actor, selectedActor,
+ "The breakpoint's client actor is correct");
+ is(aBreakpointClient.location.line, aLine,
+ "The breakpoint's client line is correct");
+ is(aBreakpointClient.conditionalExpression, aConditionalExpression,
+ "The breakpoint on line " + aLine + " should have a correct conditional expression.");
+ is("conditionalExpression" in aBreakpointClient, !!aConditionalExpression,
+ "The breakpoint on line " + aLine + " should have a correct conditional state.");
+
+ ok(isCaretPos(gPanel, aLine),
+ "The editor caret position is not properly set.");
+ });
+ }
+
+ function testNoBreakpoint(aLine) {
+ let selectedActor = gSources.selectedValue;
+ let selectedBreakpoint = gSources._selectedBreakpointItem;
+
+ 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);
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-03.js b/toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-03.js
new file mode 100644
index 000000000..18edd85ae
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-03.js
@@ -0,0 +1,90 @@
+/* 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() {
+ let gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints, gLocation;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+
+ // This test forces conditional breakpoints to be evaluated on the
+ // client-side
+ var client = gPanel.target.client;
+ client.mainRoot.traits.conditionalBreakpoints = false;
+
+ gLocation = { actor: gSources.selectedValue, line: 18 };
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
+ .then(addBreakpoint)
+ .then(setConditional)
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
+ toggleBreakpoint();
+ return finished;
+ })
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
+ toggleBreakpoint();
+ return finished;
+ })
+ .then(testConditionalExpressionOnClient)
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
+ openConditionalPopup();
+ return finished;
+ })
+ .then(testConditionalExpressionInPopup)
+ .then(() => {
+ // Reset traits back to default value
+ client.mainRoot.traits.conditionalBreakpoints = true;
+ })
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "ermahgerd");
+ });
+
+ function addBreakpoint() {
+ return gPanel.addBreakpoint(gLocation);
+ }
+
+ function setConditional(aClient) {
+ aClient.conditionalExpression = "hello";
+ }
+
+ function toggleBreakpoint() {
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelector(".dbg-breakpoint-checkbox"),
+ gDebugger);
+ }
+
+ function openConditionalPopup() {
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelector(".dbg-breakpoint"),
+ gDebugger);
+ }
+
+ function testConditionalExpressionOnClient() {
+ return gBreakpoints._getAdded(gLocation).then(aClient => {
+ is(aClient.conditionalExpression, "hello", "The expression is correct (1).");
+ });
+ }
+
+ function testConditionalExpressionInPopup() {
+ let textbox = gDebugger.document.getElementById("conditional-breakpoint-panel-textbox");
+ is(textbox.value, "hello", "The expression is correct (2).")
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-04.js b/toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-04.js
new file mode 100644
index 000000000..3197139c1
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_conditional-breakpoints-04.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that conditional breakpoints with undefined expressions
+ * are stored as plain breakpoints when re-enabling them.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
+
+function test() {
+ let gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints, gLocation;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+
+ // This test forces conditional breakpoints to be evaluated on the
+ // client-side
+ var client = gPanel.target.client;
+ client.mainRoot.traits.conditionalBreakpoints = false;
+
+ gLocation = { actor: gSources.selectedValue, line: 18 };
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
+ .then(addBreakpoint)
+ .then(setDummyConditional)
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
+ toggleBreakpoint();
+ return finished;
+ })
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
+ toggleBreakpoint();
+ return finished;
+ })
+ .then(testConditionalExpressionOnClient)
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
+ openConditionalPopup();
+ finished.then(() => ok(false, "The popup shouldn't have opened."));
+ return waitForTime(1000);
+ })
+ .then(() => {
+ // Reset traits back to default value
+ client.mainRoot.traits.conditionalBreakpoints = true;
+ })
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "ermahgerd");
+ });
+
+ function addBreakpoint() {
+ return gPanel.addBreakpoint(gLocation);
+ }
+
+ function setDummyConditional(aClient) {
+ // This happens when a conditional expression input popup is shown
+ // but the user doesn't type anything into it.
+ aClient.conditionalExpression = "";
+ }
+
+ function toggleBreakpoint() {
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelector(".dbg-breakpoint-checkbox"),
+ gDebugger);
+ }
+
+ function openConditionalPopup() {
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelector(".dbg-breakpoint"),
+ gDebugger);
+ }
+
+ function testConditionalExpressionOnClient() {
+ return gBreakpoints._getAdded(gLocation).then(aClient => {
+ if ("conditionalExpression" in aClient) {
+ ok(false, "A conditional expression shouldn't have been set.");
+ } else {
+ ok(true, "The conditional expression wasn't set, as expected.");
+ }
+ });
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_controller-evaluate-01.js b/toolkit/devtools/debugger/test/browser_dbg_controller-evaluate-01.js
new file mode 100644
index 000000000..41b98756c
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_controller-evaluate-01.js
@@ -0,0 +1,91 @@
+/* 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() {
+ let [tab,, panel] = yield initDebugger(TAB_URL);
+ let win = panel.panelWin;
+ let frames = win.DebuggerController.StackFrames;
+ let framesView = win.DebuggerView.StackFrames;
+ let sources = win.DebuggerController.SourceScripts;
+ let sourcesView = win.DebuggerView.Sources;
+ let editorView = win.DebuggerView.editor;
+ let events = win.EVENTS;
+
+ 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.
+ yield promise.all(sourcesView.attachments.map(e => sources.getText(e.source)));
+ is(sources._cache.size, 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/toolkit/devtools/debugger/test/browser_dbg_controller-evaluate-02.js b/toolkit/devtools/debugger/test/browser_dbg_controller-evaluate-02.js
new file mode 100644
index 000000000..441bac541
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_controller-evaluate-02.js
@@ -0,0 +1,66 @@
+/* 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() {
+ let [tab,, panel] = yield initDebugger(TAB_URL);
+ let win = panel.panelWin;
+ let frames = win.DebuggerController.StackFrames;
+ let framesView = win.DebuggerView.StackFrames;
+ let sources = win.DebuggerController.SourceScripts;
+ let sourcesView = win.DebuggerView.Sources;
+ let editorView = win.DebuggerView.editor;
+ let events = win.EVENTS;
+
+ 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.
+ yield promise.all(sourcesView.attachments.map(e => sources.getText(e.source)));
+ is(sources._cache.size, 2, "There should be two cached sources in the cache.");
+
+ // 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/toolkit/devtools/debugger/test/browser_dbg_debugger-statement.js b/toolkit/devtools/debugger/test/browser_dbg_debugger-statement.js
new file mode 100644
index 000000000..df63a2f4f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_debugger-statement.js
@@ -0,0 +1,89 @@
+/* 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";
+
+let gClient;
+let gTab;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect((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(closeConnection)
+ .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");
+}
+
+function closeConnection() {
+ let deferred = promise.defer();
+ gClient.close(deferred.resolve);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gClient = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_editor-contextmenu.js b/toolkit/devtools/debugger/test/browser_dbg_editor-contextmenu.js
new file mode 100644
index 000000000..00d45e77f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_editor-contextmenu.js
@@ -0,0 +1,62 @@
+/* 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;
+
+ initDebugger(TAB_URL).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/toolkit/devtools/debugger/test/browser_dbg_editor-mode.js b/toolkit/devtools/debugger/test/browser_dbg_editor-mode.js
new file mode 100644
index 000000000..dc379e517
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_editor-mode.js
@@ -0,0 +1,91 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources;
+
+function test() {
+ initDebugger(TAB_URL).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/toolkit/devtools/debugger/test/browser_dbg_event-listeners-01.js b/toolkit/devtools/debugger/test/browser_dbg_event-listeners-01.js
new file mode 100644
index 000000000..675bd64cf
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_event-listeners-01.js
@@ -0,0 +1,151 @@
+/* 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";
+
+let gClient;
+let gTab;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect((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(closeConnection)
+ .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);
+ });
+
+ sendMouseClickToTab(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;
+}
+
+function closeConnection() {
+ let deferred = promise.defer();
+ gClient.close(deferred.resolve);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gClient = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_event-listeners-02.js b/toolkit/devtools/debugger/test/browser_dbg_event-listeners-02.js
new file mode 100644
index 000000000..8f5c5e5bb
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_event-listeners-02.js
@@ -0,0 +1,127 @@
+/* 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";
+
+let gClient;
+let gTab;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect((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(closeConnection)
+ .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);
+ });
+
+ sendMouseClickToTab(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;
+}
+
+function closeConnection() {
+ let deferred = promise.defer();
+ gClient.close(deferred.resolve);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gClient = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_event-listeners-03.js b/toolkit/devtools/debugger/test/browser_dbg_event-listeners-03.js
new file mode 100644
index 000000000..0173919df
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_event-listeners-03.js
@@ -0,0 +1,86 @@
+/* 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";
+
+let gClient;
+let gTab;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect((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(closeConnection)
+ .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);
+ });
+
+ sendMouseClickToTab(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 4 event listeners in the page: button.onclick, window.onload
+ // and two more from the video element controls.
+ is(aPacket.listeners.length, 4, "Found all event listeners.");
+ aThreadClient.resume(deferred.resolve);
+ });
+
+ return deferred.promise;
+}
+
+function closeConnection() {
+ let deferred = promise.defer();
+ gClient.close(deferred.resolve);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gClient = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_file-reload.js b/toolkit/devtools/debugger/test/browser_dbg_file-reload.js
new file mode 100644
index 000000000..db1501569
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_file-reload.js
@@ -0,0 +1,63 @@
+/* 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() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ let gDebugger = aPanel.panelWin;
+ let gEditor = gDebugger.DebuggerView.editor;
+ let gSources = gDebugger.DebuggerView.Sources;
+ let gControllerSources = gDebugger.DebuggerController.SourceScripts;
+
+ Task.spawn(function() {
+ yield waitForSourceShown(aPanel, JS_URL);
+
+ is(gSources.itemCount, 1,
+ "There should be one source displayed in the view.")
+ is(getSelectedSourceURL(gSources), JS_URL,
+ "The correct source is currently selected in the view.");
+ ok(gEditor.getText().contains("bacon"),
+ "The currently shown source contains bacon. Mmm, delicious!");
+
+ let { source } = gSources.selectedItem.attachment;
+ let [, firstText] = yield gControllerSources.getText(source);
+ let 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(gSources.itemCount, 1,
+ "There should be one source displayed in the view after reloading.")
+ is(getSelectedSourceURL(gSources), JS_URL,
+ "The correct source is currently selected in the view after reloading.");
+ ok(gEditor.getText().contains("bacon"),
+ "The newly shown source contains bacon. Mmm, delicious!");
+
+ ({ source } = gSources.selectedItem.attachment);
+ let [, secondText] = yield gControllerSources.getText(source);
+ let 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/toolkit/devtools/debugger/test/browser_dbg_function-display-name.js b/toolkit/devtools/debugger/test/browser_dbg_function-display-name.js
new file mode 100644
index 000000000..0b0ef9433
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_function-display-name.js
@@ -0,0 +1,61 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ testAnonCall();
+ });
+}
+
+function testAnonCall() {
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 15).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();
+ });
+
+ callInTab(gTab, "evalCall");
+}
+
+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/toolkit/devtools/debugger/test/browser_dbg_global-method-override.js b/toolkit/devtools/debugger/test/browser_dbg_global-method-override.js
new file mode 100644
index 000000000..e3ca15e9b
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_global-method-override.js
@@ -0,0 +1,20 @@
+/* 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() {
+ initDebugger(TAB_URL).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/toolkit/devtools/debugger/test/browser_dbg_globalactor.js b/toolkit/devtools/debugger/test/browser_dbg_globalactor.js
new file mode 100644
index 000000000..fa21a6f7f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_globalactor.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check extension-added global actor API.
+ */
+
+const CHROME_URL = "chrome://mochitests/content/browser/browser/devtools/debugger/test/"
+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((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.contains("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 conn = transport._serverConnection;
+
+ // First we look for the pool of global actors.
+ let extraPools = conn._extraPools;
+ let globalPool;
+
+ let actorPrefix = conn._prefix + "test_one";
+ let count = 0;
+ for (let pool of 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(finish);
+ });
+ });
+ });
+ });
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_hide-toolbar-buttons.js b/toolkit/devtools/debugger/test/browser_dbg_hide-toolbar-buttons.js
new file mode 100644
index 000000000..41f83addb
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_hide-toolbar-buttons.js
@@ -0,0 +1,32 @@
+/* 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";
+
+let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+let { RootActor } = devtools.require("devtools/server/actors/root");
+
+function test() {
+ let gTab, gDebuggee, gPanel, gDebugger;
+ let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
+
+ RootActor.prototype.traits.noBlackBoxing = true;
+ RootActor.prototype.traits.noPrettyPrinting = true;
+
+ initDebugger(TAB_URL).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/toolkit/devtools/debugger/test/browser_dbg_hit-counts-01.js b/toolkit/devtools/debugger/test/browser_dbg_hit-counts-01.js
new file mode 100644
index 000000000..841362b7a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_hit-counts-01.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Evaluating two functions on the same line and checking for correct hit count
+ * for both of them in CodeMirror's gutter.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_same-line-functions.html";
+const CODE_URL = "code_same-line-functions.js";
+
+let gTab, gPanel, gDebugger;
+let gEditor;
+
+function test() {
+ Task.async(function* () {
+ yield pushPrefs(["devtools.debugger.tracer", true]);
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+
+ Task.async(function* () {
+ yield waitForSourceShown(gPanel, CODE_URL);
+ yield startTracing(gPanel);
+
+ clickButton();
+
+ yield waitForClientEvents(aPanel, "traces");
+
+ testHitCounts();
+
+ yield stopTracing(gPanel);
+ yield popPrefs();
+ yield closeDebuggerAndFinish(gPanel);
+ })();
+ });
+ })().catch(e => {
+ ok(false, "Got an error: " + e.message + "\n" + e.stack);
+ });
+}
+
+function clickButton() {
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+}
+
+function testHitCounts() {
+ let marker = gEditor.getMarker(0, 'hit-counts');
+
+ is(marker.innerHTML, "1\u00D7|1\u00D7",
+ "Both functions should be hit only once.");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_hit-counts-02.js b/toolkit/devtools/debugger/test/browser_dbg_hit-counts-02.js
new file mode 100644
index 000000000..fb9788e9d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_hit-counts-02.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * When tracing is stopped all hit counters should be cleared.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_same-line-functions.html";
+const CODE_URL = "code_same-line-functions.js";
+
+let gTab, gPanel, gDebugger;
+let gEditor;
+
+function test() {
+ Task.async(function* () {
+ yield pushPrefs(["devtools.debugger.tracer", true]);
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+
+ Task.async(function* () {
+ yield waitForSourceShown(gPanel, CODE_URL);
+ yield startTracing(gPanel);
+
+ clickButton();
+
+ yield waitForClientEvents(aPanel, "traces");
+
+ testHitCountsBeforeStopping();
+
+ yield stopTracing(gPanel);
+
+ testHitCountsAfterStopping();
+
+ yield popPrefs();
+ yield closeDebuggerAndFinish(gPanel);
+ })();
+ });
+ })().catch(e => {
+ ok(false, "Got an error: " + e.message + "\n" + e.stack);
+ });
+}
+
+function clickButton() {
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+}
+
+function testHitCountsBeforeStopping() {
+ let marker = gEditor.getMarker(0, 'hit-counts');
+ ok(marker, "A counter should exists.");
+}
+
+function testHitCountsAfterStopping() {
+ let marker = gEditor.getMarker(0, 'hit-counts');
+ is(marker, undefined, "A counter should be cleared.");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_host-layout.js b/toolkit/devtools/debugger/test/browser_dbg_host-layout.js
new file mode 100644
index 000000000..66b9c70bd
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_host-layout.js
@@ -0,0 +1,104 @@
+/* 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.
+ */
+
+let gDefaultHostType = Services.prefs.getCharPref("devtools.toolbox.host");
+
+function test() {
+ Task.spawn(function() {
+ yield testHosts(["bottom", "side", "window"], ["horizontal", "vertical", "horizontal"]);
+ yield testHosts(["side", "bottom", "side"], ["vertical", "horizontal", "vertical"]);
+ yield testHosts(["bottom", "side", "bottom"], ["horizontal", "vertical", "horizontal"]);
+ yield testHosts(["side", "window", "side"], ["vertical", "horizontal", "vertical"]);
+ yield testHosts(["window", "side", "window"], ["horizontal", "vertical", "horizontal"]);
+ finish();
+ });
+}
+
+function testHosts(aHostTypes, aLayoutTypes) {
+ let [firstHost, secondHost, thirdHost] = aHostTypes;
+ let [firstLayout, secondLayout, thirdLayout] = aLayoutTypes;
+
+ Services.prefs.setCharPref("devtools.toolbox.host", firstHost);
+
+ return Task.spawn(function() {
+ let [tab, debuggee, panel] = yield initDebugger("about:blank");
+ yield testHost(tab, panel, 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 = once(gDebugger, gDebugger.EVENTS.LAYOUT_CHANGED);
+ let hostChanged = gToolbox.switchHost(aHostType);
+
+ yield hostChanged;
+ ok(true, "The toolbox's host has changed.");
+
+ yield layoutChanged;
+ ok(true, "The debugger's layout has changed.");
+
+ yield testHost(aTab, aPanel, aHostType, aLayoutType);
+ });
+
+ function once(aTarget, aEvent) {
+ let deferred = promise.defer();
+ aTarget.once(aEvent, deferred.resolve);
+ return deferred.promise;
+ }
+}
+
+function testHost(aTab, 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._sourcesPane.parentNode.id, "debugger-widgets",
+ "The sources pane's parent is correct for the horizontal layout.");
+ is(gView._instrumentsPane.parentNode.id, "debugger-widgets",
+ "The instruments pane's parent is correct for the horizontal layout.");
+ } else {
+ is(gView._sourcesPane.parentNode.id, "vertical-layout-panes-container",
+ "The 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 panes = gDebugger.document.getElementById("vertical-layout-panes-container").childNodes;
+
+ if (aLayoutType == "horizontal") {
+ is(widgets.length, 7, // 2 panes, 1 editor, 3 splitters and a phantom box.
+ "Found the correct number of debugger widgets.");
+ is(panes.length, 1, // 1 lonely splitter in the phantom box.
+ "Found the correct number of debugger panes.");
+ } else {
+ is(widgets.length, 5, // 1 editor, 3 splitters and a phantom box.
+ "Found the correct number of debugger widgets.");
+ is(panes.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/toolkit/devtools/debugger/test/browser_dbg_iframes.js b/toolkit/devtools/debugger/test/browser_dbg_iframes.js
new file mode 100644
index 000000000..b920a85a0
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_iframes.js
@@ -0,0 +1,67 @@
+/* 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;
+
+ initDebugger(TAB_URL).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;
+
+ waitForSourceShown(gPanel, "inline-debugger-statement.html")
+ .then(checkIframeSource)
+ .then(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/toolkit/devtools/debugger/test/browser_dbg_instruments-pane-collapse.js b/toolkit/devtools/debugger/test/browser_dbg_instruments-pane-collapse.js
new file mode 100644
index 000000000..d3f8efd3f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_instruments-pane-collapse.js
@@ -0,0 +1,152 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gPrefs, gOptions;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gPrefs = gDebugger.Prefs;
+ gOptions = gDebugger.DebuggerView.Options;
+
+ testPanesState();
+
+ gDebugger.DebuggerView.toggleInstrumentsPane({ visible: true, animated: false });
+
+ testInstrumentsPaneCollapse();
+ testPanesStartupPref();
+
+ closeDebuggerAndFinish(gPanel);
+ });
+}
+
+function testPanesState() {
+ let instrumentsPane =
+ gDebugger.document.getElementById("instruments-pane");
+ let instrumentsPaneToggleButton =
+ gDebugger.document.getElementById("instruments-pane-toggle");
+
+ ok(instrumentsPane.hasAttribute("pane-collapsed") &&
+ instrumentsPaneToggleButton.hasAttribute("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.hasAttribute("pane-collapsed") &&
+ !instrumentsPaneToggleButton.hasAttribute("pane-collapsed"),
+ "The instruments pane should at this point be visible.");
+
+ gDebugger.DebuggerView.toggleInstrumentsPane({ visible: false, animated: true });
+
+ 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.hasAttribute("pane-collapsed") &&
+ instrumentsPaneToggleButton.hasAttribute("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.hasAttribute("pane-collapsed") &&
+ !instrumentsPaneToggleButton.hasAttribute("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.hasAttribute("pane-collapsed") &&
+ !instrumentsPaneToggleButton.hasAttribute("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.hasAttribute("pane-collapsed") &&
+ !instrumentsPaneToggleButton.hasAttribute("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.hasAttribute("pane-collapsed") &&
+ !instrumentsPaneToggleButton.hasAttribute("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/toolkit/devtools/debugger/test/browser_dbg_interrupts.js b/toolkit/devtools/debugger/test/browser_dbg_interrupts.js
new file mode 100644
index 000000000..aedf87697
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_interrupts.js
@@ -0,0 +1,85 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints, gTarget, gResumeButton, gResumeKey, gThreadClient;
+
+ initDebugger(TAB_URL).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");
+
+ waitForSourceShown(gPanel, "-01.js")
+ .then(() => { gTarget.on("thread-paused", failOnPause); })
+ .then(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("checked"), "Resume button is not checked");
+ let oncePaused = gTarget.once("thread-paused");
+ EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger);
+
+ return oncePaused
+ .then(() => {
+ is (gResumeButton.getAttribute("checked"), "true", "Resume button is checked");
+ })
+ .then(() => gThreadClient.resume())
+ .then(() => ensureThreadClientState(gPanel, "resumed"))
+ }
+
+ function testResumeKeyboard() {
+ let key = gResumeKey.getAttribute("keycode");
+ info ("Triggering a pause with keyboard (" + key + "), expecting a thread-paused");
+
+ ok (!gResumeButton.hasAttribute("checked"), "Resume button is not checked");
+ let oncePaused = gTarget.once("thread-paused");
+ EventUtils.synthesizeKey(key, { }, gDebugger);
+
+ return oncePaused
+ .then(() => {
+ is (gResumeButton.getAttribute("checked"), "true", "Resume button is checked");
+ })
+ .then(() => gThreadClient.resume())
+ .then(() => ensureThreadClientState(gPanel, "resumed"))
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_listaddons.js b/toolkit/devtools/debugger/test/browser_dbg_listaddons.js
new file mode 100644
index 000000000..bf3014ef3
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_listaddons.js
@@ -0,0 +1,114 @@
+/* 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_URL = EXAMPLE_URL + "addon1.xpi";
+const ADDON2_URL = EXAMPLE_URL + "addon2.xpi";
+
+let gAddon1, gAddon1Actor, gAddon2, gAddon2Actor, gClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect((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(closeConnection)
+ .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 addAddon(ADDON1_URL).then(aAddon => {
+ gAddon1 = aAddon;
+
+ return getAddonActorForUrl(gClient, ADDON1_URL).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 addAddon(ADDON2_URL).then(aAddon => {
+ gAddon2 = aAddon;
+
+ return getAddonActorForUrl(gClient, ADDON1_URL).then(aFirstGrip => {
+ return getAddonActorForUrl(gClient, ADDON2_URL).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;
+ });
+
+ removeAddon(gAddon1).then(() => {
+ return getAddonActorForUrl(gClient, ADDON1_URL).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;
+ });
+
+ removeAddon(gAddon2).then(() => {
+ return getAddonActorForUrl(gClient, ADDON2_URL).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.");
+ });
+ });
+}
+
+function closeConnection() {
+ let deferred = promise.defer();
+ gClient.close(deferred.resolve);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gAddon1 = null;
+ gAddon1Actor = null;
+ gAddon2 = null;
+ gAddon2Actor = null;
+ gClient = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_listtabs-01.js b/toolkit/devtools/debugger/test/browser_dbg_listtabs-01.js
new file mode 100644
index 000000000..0f6bc608d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_listtabs-01.js
@@ -0,0 +1,101 @@
+/* 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";
+
+let gTab1, gTab1Actor, gTab2, gTab2Actor, gClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect((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(closeConnection)
+ .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.type, "exited", "Tab should consider itself exited.");
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+ });
+}
+
+function closeConnection() {
+ let deferred = promise.defer();
+ gClient.close(deferred.resolve);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab1 = null;
+ gTab1Actor = null;
+ gTab2 = null;
+ gTab2Actor = null;
+ gClient = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_listtabs-02.js b/toolkit/devtools/debugger/test/browser_dbg_listtabs-02.js
new file mode 100644
index 000000000..d9878a70a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_listtabs-02.js
@@ -0,0 +1,218 @@
+/* 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.
+ */
+
+let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+let { BrowserTabList } = devtools.require("devtools/server/actors/webbrowser");
+
+let 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.
+let gTabList;
+let gFirstActor, gActorA;
+let gTabA, gTabB, gTabC;
+let gNewWindow;
+
+// Stock onListChanged handler.
+let 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: " + [a.url for (a of tabActors)]);
+ gActorA = [a for (a of tabActors) if (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, "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: " + [a.url for (a of tabActors)]);
+ gActorA = [a for (a of tabActors) if (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, "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: " + [a.url for (a of tabActors)]);
+ gActorA = [a for (a of tabActors) if (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: " + [a.url for (a of tabActors)]);
+ gActorA = [a for (a of tabActors) if (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, "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/toolkit/devtools/debugger/test/browser_dbg_listtabs-03.js b/toolkit/devtools/debugger/test/browser_dbg_listtabs-03.js
new file mode 100644
index 000000000..26ccc6837
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_listtabs-03.js
@@ -0,0 +1,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";
+
+let gTab1, gTab1Actor, gTab2, gTab2Actor, gClient;
+
+function listTabs() {
+ let deferred = promise.defer();
+
+ gClient.listTabs(aResponse => {
+ deferred.resolve(aResponse.tabs);
+ });
+
+ return deferred.promise;
+}
+
+function request(params) {
+ let deferred = promise.defer();
+ gClient.request(params, deferred.resolve);
+ return deferred.promise;
+}
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect(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 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 request({ to: tabGrip.actor, type: "attach" });
+ is(response.type, "tabAttached", "Should have attached");
+
+ tabs = yield listTabs();
+
+ response = yield 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 request({ to: tabGrip.actor, type: "attach" });
+ is(response.type, "tabAttached", "Should have attached");
+ response = yield request({ to: tabGrip.actor, type: "detach" });
+ is(response.type, "detached", "Should have detached");
+
+ yield removeTab(tab);
+ yield closeConnection();
+ finish();
+ }));
+}
+
+function closeConnection() {
+ let deferred = promise.defer();
+ gClient.close(deferred.resolve);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab1 = null;
+ gTab1Actor = null;
+ gTab2 = null;
+ gTab2Actor = null;
+ gClient = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_location-changes-01-simple.js b/toolkit/devtools/debugger/test/browser_dbg_location-changes-01-simple.js
new file mode 100644
index 000000000..17feedb91
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_location-changes-01-simple.js
@@ -0,0 +1,69 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gFrames;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 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(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).");
+
+ gDebugger.gThreadClient.resume(() => {
+ testLocationChange();
+ });
+}
+
+function testLocationChange() {
+ navigateActiveTabTo(gPanel, "about:blank", gDebugger.EVENTS.SOURCES_ADDED).then(() => {
+ closeDebuggerAndFinish(gPanel);
+ });
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gFrames = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_location-changes-02-blank.js b/toolkit/devtools/debugger/test/browser_dbg_location-changes-02-blank.js
new file mode 100644
index 000000000..20a23ca7c
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_location-changes-02-blank.js
@@ -0,0 +1,60 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gFrames;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 14).then(testLocationChange);
+ callInTab(gTab, "simpleCall");
+ });
+}
+
+function testLocationChange() {
+ navigateActiveTabTo(gPanel, "about:blank", gDebugger.EVENTS.SOURCES_ADDED).then(() => {
+ 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);
+ });
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gFrames = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_location-changes-03-new.js b/toolkit/devtools/debugger/test/browser_dbg_location-changes-03-new.js
new file mode 100644
index 000000000..b680b2ff3
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_location-changes-03-new.js
@@ -0,0 +1,62 @@
+/* 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";
+
+let gTab, gDebuggee, gPanel, gDebugger;
+let gEditor, gSources, gFrames;
+
+function test() {
+ initDebugger(TAB_URL_1).then(([aTab, aDebuggee, aPanel]) => {
+ gTab = aTab;
+ gDebuggee = aDebuggee;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 14).then(testLocationChange);
+ gDebuggee.simpleCall();
+ });
+}
+
+function testLocationChange() {
+ navigateActiveTabTo(gPanel, TAB_URL_2, gDebugger.EVENTS.SOURCES_ADDED).then(() => {
+ 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 not 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.");
+
+ closeDebuggerAndFinish(gPanel);
+ });
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gDebuggee = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gFrames = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_location-changes-04-breakpoint.js b/toolkit/devtools/debugger/test/browser_dbg_location-changes-04-breakpoint.js
new file mode 100644
index 000000000..f3f37abd0
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_location-changes-04-breakpoint.js
@@ -0,0 +1,201 @@
+/* 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";
+
+let gTab, gDebuggee, gPanel, gDebugger;
+let gEditor, gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+ gTab = aTab;
+ gDebuggee = aDebuggee;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 17).then(addBreakpoint);
+ gDebuggee.runDebuggerStatement();
+ });
+}
+
+function addBreakpoint() {
+ waitForSourceAndCaret(gPanel, ".js", 5).then(() => {
+ ok(true,
+ "Switched to the desired function when adding a breakpoint " +
+ "but not passing { noEditorUpdate: true } as an option.");
+
+ testResume();
+ });
+
+ gPanel.addBreakpoint({ actor: getSourceActor(gSources, SOURCE_URL), line: 5 });
+}
+
+function testResume() {
+ is(gDebugger.gThreadClient.state, "paused",
+ "The breakpoint wasn't hit yet (1).");
+ is(getSelectedSourceURL(gSources), SOURCE_URL,
+ "The currently shown source is incorrect (1).");
+ ok(isCaretPos(gPanel, 5),
+ "The source editor caret position is incorrect (1).");
+
+ gDebugger.gThreadClient.resume(testClick);
+}
+
+function testClick() {
+ isnot(gDebugger.gThreadClient.state, "paused",
+ "The breakpoint wasn't hit yet (2).");
+ is(getSelectedSourceURL(gSources), SOURCE_URL,
+ "The currently shown source is incorrect (2).");
+ ok(isCaretPos(gPanel, 5),
+ "The source editor caret position is incorrect (2).");
+
+ gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.why.type, "breakpoint",
+ "Execution has advanced to the breakpoint.");
+ isnot(aPacket.why.type, "debuggerStatement",
+ "The breakpoint was hit before the debugger statement.");
+
+ ensureCaretAt(gPanel, 5, 1, true).then(afterBreakpointHit);
+ });
+
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebuggee.document.querySelector("button"),
+ gDebuggee);
+}
+
+function afterBreakpointHit() {
+ 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).");
+
+ gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.why.type, "debuggerStatement",
+ "Execution has advanced to the next line.");
+ isnot(aPacket.why.type, "breakpoint",
+ "No ghost breakpoint was hit.");
+
+ ensureCaretAt(gPanel, 6, 1, true).then(afterDebuggerStatementHit);
+ });
+
+ gDebugger.gThreadClient.resume();
+}
+
+function afterDebuggerStatementHit() {
+ 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).");
+
+ promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.NEW_SOURCE),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCES_ADDED),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN),
+ reloadActiveTab(gPanel, gDebugger.EVENTS.BREAKPOINT_SHOWN_IN_EDITOR),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_SHOWN_IN_PANE)
+ ]).then(testClickAgain);
+}
+
+function testClickAgain() {
+ 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).");
+
+ gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.why.type, "breakpoint",
+ "Execution has advanced to the breakpoint.");
+ isnot(aPacket.why.type, "debuggerStatement",
+ "The breakpoint was hit before the debugger statement.");
+
+ ensureCaretAt(gPanel, 5, 1, true).then(afterBreakpointHitAgain);
+ });
+
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebuggee.document.querySelector("button"),
+ gDebuggee);
+}
+
+function afterBreakpointHitAgain() {
+ 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).");
+
+ gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+ is(aPacket.why.type, "debuggerStatement",
+ "Execution has advanced to the next line.");
+ isnot(aPacket.why.type, "breakpoint",
+ "No ghost breakpoint was hit.");
+
+ ensureCaretAt(gPanel, 6, 1, true).then(afterDebuggerStatementHitAgain);
+ });
+
+ gDebugger.gThreadClient.resume();
+}
+
+function afterDebuggerStatementHitAgain() {
+ 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).");
+
+ showSecondSource();
+}
+
+function showSecondSource() {
+ gDebugger.once(gDebugger.EVENTS.SOURCE_SHOWN, () => {
+ 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.");
+
+ ensureCaretAt(gPanel, 1, 1, true).then(showFirstSourceAgain);
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.querySelectorAll(".side-menu-widget-item-contents")[1],
+ gDebugger);
+}
+
+function showFirstSourceAgain() {
+ gDebugger.once(gDebugger.EVENTS.SOURCE_SHOWN, () => {
+ 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.");
+
+ ensureCaretAt(gPanel, 6, 1, true).then(() => resumeDebuggerThenCloseAndFinish(gPanel));
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.querySelectorAll(".side-menu-widget-item-contents")[0],
+ gDebugger);
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gDebuggee = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_multiple-windows.js b/toolkit/devtools/debugger/test/browser_dbg_multiple-windows.js
new file mode 100644
index 000000000..f842a3fc1
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_multiple-windows.js
@@ -0,0 +1,169 @@
+/* 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";
+
+let gNewTab, gNewWindow;
+let gClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect((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(closeConnection)
+ .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();
+ });
+}
+
+function closeConnection() {
+ let deferred = promise.defer();
+ gClient.close(deferred.resolve);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gNewTab = null;
+ gNewWindow = null;
+ gClient = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_navigation.js b/toolkit/devtools/debugger/test/browser_dbg_navigation.js
new file mode 100644
index 000000000..39f4612c8
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_navigation.js
@@ -0,0 +1,73 @@
+/* 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";
+
+let gClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect((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");
+ gClient.close(deferred.resolve);
+ });
+
+ removeTab(gBrowser.selectedTab);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gClient = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_no-page-sources.js b/toolkit/devtools/debugger/test/browser_dbg_no-page-sources.js
new file mode 100644
index 000000000..997280f37
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_no-page-sources.js
@@ -0,0 +1,50 @@
+/* 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";
+
+let gTab, gDebuggee, gPanel, gDebugger;
+let gEditor, gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+ gTab = aTab;
+ gDebuggee = aDebuggee;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCES_ADDED)
+ .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/toolkit/devtools/debugger/test/browser_dbg_on-pause-highlight.js b/toolkit/devtools/debugger/test/browser_dbg_on-pause-highlight.js
new file mode 100644
index 000000000..d173cacad
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_on-pause-highlight.js
@@ -0,0 +1,75 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gToolbox, gToolboxTab;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gToolbox = gPanel._toolbox;
+ gToolboxTab = gToolbox.doc.getElementById("toolbox-tab-jsdebugger");
+
+ waitForSourceShown(gPanel, ".html").then(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);
+}
+
+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/toolkit/devtools/debugger/test/browser_dbg_on-pause-raise.js b/toolkit/devtools/debugger/test/browser_dbg_on-pause-raise.js
new file mode 100644
index 000000000..ccf74c828
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_on-pause-raise.js
@@ -0,0 +1,139 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gFocusedWindow, gToolbox, gToolboxTab;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gToolbox = gPanel._toolbox;
+ gToolboxTab = gToolbox.doc.getElementById("toolbox-tab-jsdebugger");
+
+ waitForSourceShown(gPanel, ".html").then(performTest);
+ });
+}
+
+function performTest() {
+ addTab(TAB_URL).then(aTab => {
+ isnot(aTab, gTab,
+ "The newly added tab is different from the debugger's tab.");
+ is(gBrowser.selectedTab, aTab,
+ "Debugger's tab is not the selected tab.");
+
+ gFocusedWindow = window;
+ testPause();
+ });
+}
+
+function focusMainWindow() {
+ // Make sure toolbox is not focused.
+ window.addEventListener("focus", onFocus, true);
+ info("Focusing main window.")
+
+ // Execute soon to avoid any race conditions between toolbox and main window
+ // getting focused.
+ executeSoon(() => {
+ window.focus();
+ });
+}
+
+function onFocus() {
+ window.removeEventListener("focus", onFocus, true);
+ info("Main window focused.")
+
+ gFocusedWindow = window;
+ testPause();
+}
+
+function testPause() {
+ is(gDebugger.gThreadClient.paused, false,
+ "Should be running after starting the test.");
+
+ is(gFocusedWindow, window,
+ "Main window is the top level window before pause.");
+
+ if (gToolbox.hostType == devtools.Toolbox.HostType.WINDOW) {
+ gToolbox._host._window.onfocus = () => {
+ gFocusedWindow = gToolbox._host._window;
+ };
+ }
+
+ gDebugger.gThreadClient.addOneTimeListener("paused", () => {
+ if (gToolbox.hostType == devtools.Toolbox.HostType.WINDOW) {
+ is(gFocusedWindow, gToolbox._host._window,
+ "Toolbox window is the top level window on pause.");
+ } else {
+ is(gBrowser.selectedTab, gTab,
+ "Debugger's tab got selected.");
+ }
+ 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);
+}
+
+function testResume() {
+ gDebugger.gThreadClient.addOneTimeListener("resumed", () => {
+ gToolbox.selectTool("webconsole").then(() => {
+ ok(!gToolboxTab.hasAttribute("highlighted") ||
+ gToolboxTab.getAttribute("highlighted") != "true",
+ "The highlighted class is not present now after the resume");
+ ok(!gToolboxTab.hasAttribute("selected") ||
+ gToolboxTab.getAttribute("selected") != "true",
+ "The tab is not selected");
+ }).then(maybeEndTest);
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("resume"),
+ gDebugger);
+}
+
+function maybeEndTest() {
+ if (gToolbox.hostType == devtools.Toolbox.HostType.BOTTOM) {
+ info("Switching to a toolbox window host.");
+ gToolbox.switchHost(devtools.Toolbox.HostType.WINDOW).then(focusMainWindow);
+ } else {
+ info("Switching to main window host.");
+ gToolbox.switchHost(devtools.Toolbox.HostType.BOTTOM).then(() => closeDebuggerAndFinish(gPanel));
+ }
+}
+
+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", devtools.Toolbox.HostType.BOTTOM);
+
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+
+ gFocusedWindow = null;
+ gToolbox = null;
+ gToolboxTab = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_optimized-out-vars.js b/toolkit/devtools/debugger/test/browser_dbg_optimized-out-vars.js
new file mode 100644
index 000000000..a88f7d04a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_optimized-out-vars.js
@@ -0,0 +1,45 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ gDebugger = panel.panelWin;
+ sources = gDebugger.DebuggerView.Sources;
+
+ yield waitForSourceShown(panel, ".html");
+ 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.
+ sendMouseClickToTab(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/toolkit/devtools/debugger/test/browser_dbg_panel-size.js b/toolkit/devtools/debugger/test/browser_dbg_panel-size.js
new file mode 100644
index 000000000..8ae8aad6d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_panel-size.js
@@ -0,0 +1,82 @@
+/* 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;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gPrefs = gDebugger.Prefs;
+ gSources = gDebugger.document.getElementById("sources-pane");
+ gInstruments = gDebugger.document.getElementById("instruments-pane");
+
+ waitForSourceShown(gPanel, ".html").then(performTest);
+ });
+
+ function performTest() {
+ let preferredSw = Services.prefs.getIntPref("devtools.debugger.ui.panes-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 == preferredSw) || (someWidth2 == preferredIw));
+
+ info("Preferred sources width: " + preferredSw);
+ info("Preferred instruments width: " + preferredIw);
+ info("Generated sources width: " + someWidth1);
+ info("Generated instruments width: " + someWidth2);
+
+ ok(gPrefs.sourcesWidth,
+ "The debugger preferences should have a saved sourcesWidth value.");
+ ok(gPrefs.instrumentsWidth,
+ "The debugger preferences should have a saved instrumentsWidth value.");
+
+ is(gPrefs.sourcesWidth, preferredSw,
+ "The debugger preferences should have a correct sourcesWidth value.");
+ is(gPrefs.instrumentsWidth, preferredIw,
+ "The debugger preferences should have a correct instrumentsWidth value.");
+
+ is(gSources.getAttribute("width"), gPrefs.sourcesWidth,
+ "The 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.sourcesWidth, preferredSw,
+ "The 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.sourcesWidth,
+ "The 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.sourcesWidth, someWidth1,
+ "The 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-sources-width", preferredSw);
+ Services.prefs.setIntPref("devtools.debugger.ui.panes-instruments-width", preferredIw);
+
+ finish();
+ });
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_parser-01.js b/toolkit/devtools/debugger/test/browser_dbg_parser-01.js
new file mode 100644
index 000000000..f2bb87c0a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_parser-01.js
@@ -0,0 +1,31 @@
+/* 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:///modules/devtools/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/toolkit/devtools/debugger/test/browser_dbg_parser-02.js b/toolkit/devtools/debugger/test/browser_dbg_parser-02.js
new file mode 100644
index 000000000..45508d864
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_parser-02.js
@@ -0,0 +1,26 @@
+/* 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:///modules/devtools/Parser.jsm", {});
+
+ let source = "let x + 42;";
+ let parser = new Parser();
+ 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/toolkit/devtools/debugger/test/browser_dbg_parser-03.js b/toolkit/devtools/debugger/test/browser_dbg_parser-03.js
new file mode 100644
index 000000000..a4c0fce91
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_parser-03.js
@@ -0,0 +1,77 @@
+/* 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:///modules/devtools/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/toolkit/devtools/debugger/test/browser_dbg_parser-04.js b/toolkit/devtools/debugger/test/browser_dbg_parser-04.js
new file mode 100644
index 000000000..2ef653bf3
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_parser-04.js
@@ -0,0 +1,54 @@
+/* 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:///modules/devtools/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, 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/toolkit/devtools/debugger/test/browser_dbg_parser-05.js b/toolkit/devtools/debugger/test/browser_dbg_parser-05.js
new file mode 100644
index 000000000..4c1d935a2
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_parser-05.js
@@ -0,0 +1,43 @@
+/* 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:///modules/devtools/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/toolkit/devtools/debugger/test/browser_dbg_parser-06.js b/toolkit/devtools/debugger/test/browser_dbg_parser-06.js
new file mode 100644
index 000000000..cab13235e
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_parser-06.js
@@ -0,0 +1,78 @@
+/* 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:///modules/devtools/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/toolkit/devtools/debugger/test/browser_dbg_parser-07.js b/toolkit/devtools/debugger/test/browser_dbg_parser-07.js
new file mode 100644
index 000000000..099c16301
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_parser-07.js
@@ -0,0 +1,55 @@
+/* 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:///modules/devtools/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/toolkit/devtools/debugger/test/browser_dbg_parser-08.js b/toolkit/devtools/debugger/test/browser_dbg_parser-08.js
new file mode 100644
index 000000000..0286d9be2
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_parser-08.js
@@ -0,0 +1,289 @@
+/* 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:///modules/devtools/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/toolkit/devtools/debugger/test/browser_dbg_parser-09.js b/toolkit/devtools/debugger/test/browser_dbg_parser-09.js
new file mode 100644
index 000000000..a8a9ad2c3
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_parser-09.js
@@ -0,0 +1,290 @@
+/* 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:///modules/devtools/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 == "ArrowExpression", {
+ name: "foo",
+ chain: null,
+ loc: [[1, 4], [1, 7]]
+ });
+ verify("\nvar\nfoo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowExpression", {
+ name: "foo",
+ chain: null,
+ loc: [[3, 0], [3, 3]]
+ });
+
+ // AssignmentExpression
+
+ verify("foo=()=>{}", e => e.type == "ArrowExpression",
+ { name: "foo", chain: [], loc: [[1, 0], [1, 3]] });
+
+ verify("\nfoo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowExpression",
+ { name: "foo", chain: [], loc: [[2, 0], [2, 3]] });
+
+ verify("foo.bar=()=>{}", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["foo"], loc: [[1, 0], [1, 7]] });
+
+ verify("\nfoo.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["foo"], loc: [[2, 0], [2, 7]] });
+
+ verify("this.foo=()=>{}", e => e.type == "ArrowExpression",
+ { name: "foo", chain: ["this"], loc: [[1, 0], [1, 8]] });
+
+ verify("\nthis.foo\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowExpression",
+ { name: "foo", chain: ["this"], loc: [[2, 0], [2, 8]] });
+
+ verify("this.foo.bar=()=>{}", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["this", "foo"], loc: [[1, 0], [1, 12]] });
+
+ verify("\nthis.foo.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["this", "foo"], loc: [[2, 0], [2, 12]] });
+
+ verify("foo.this.bar=()=>{}", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["foo", "this"], loc: [[1, 0], [1, 12]] });
+
+ verify("\nfoo.this.bar\n=\n(\n)\n=>\n{\n}\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["foo", "this"], loc: [[2, 0], [2, 12]] });
+
+ // ObjectExpression
+
+ verify("({foo:()=>{}})", e => e.type == "ArrowExpression",
+ { name: "foo", chain: [], loc: [[1, 2], [1, 5]] });
+
+ verify("(\n{\nfoo\n:\n(\n)\n=>\n{\n}\n}\n)", e => e.type == "ArrowExpression",
+ { name: "foo", chain: [], loc: [[3, 0], [3, 3]] });
+
+ verify("({foo:{bar:()=>{}}})", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["foo"], loc: [[1, 7], [1, 10]] });
+
+ verify("(\n{\nfoo\n:\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n}\n)", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] });
+
+ // AssignmentExpression + ObjectExpression
+
+ verify("foo={bar:()=>{}}", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["foo"], loc: [[1, 5], [1, 8]] });
+
+ verify("\nfoo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("foo={bar:{baz:()=>{}}}", e => e.type == "ArrowExpression",
+ { 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}\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("nested.foo={bar:()=>{}}", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["nested", "foo"], loc: [[1, 12], [1, 15]] });
+
+ verify("\nnested.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["nested", "foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("nested.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowExpression",
+ { 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}\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["nested", "foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("this.foo={bar:()=>{}}", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["this", "foo"], loc: [[1, 10], [1, 13]] });
+
+ verify("\nthis.foo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["this", "foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("this.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowExpression",
+ { 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}\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["this", "foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("this.nested.foo={bar:()=>{}}", e => e.type == "ArrowExpression",
+ { 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}\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["this", "nested", "foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("this.nested.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowExpression",
+ { 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}\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("nested.this.foo={bar:()=>{}}", e => e.type == "ArrowExpression",
+ { 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}\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["nested", "this", "foo"], loc: [[5, 0], [5, 3]] });
+
+ verify("nested.this.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowExpression",
+ { 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}\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[8, 0], [8, 3]] });
+
+ // VariableDeclarator + AssignmentExpression + ObjectExpression
+
+ verify("let foo={bar:()=>{}}", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["foo"], loc: [[1, 9], [1, 12]] });
+
+ verify("\nlet\nfoo\n=\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] });
+
+ verify("let foo={bar:{baz:()=>{}}}", e => e.type == "ArrowExpression",
+ { 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}\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["foo", "bar"], loc: [[9, 0], [9, 3]] });
+
+ // New/CallExpression + AssignmentExpression + ObjectExpression
+
+ verify("foo({bar:()=>{}})", e => e.type == "ArrowExpression",
+ { name: "bar", chain: [], loc: [[1, 5], [1, 8]] });
+
+ verify("\nfoo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
+ { 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)\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("nested.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
+ { name: "bar", chain: [], loc: [[1, 12], [1, 15]] });
+
+ verify("\nnested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
+ { 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)\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("this.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
+ { name: "bar", chain: [], loc: [[1, 10], [1, 13]] });
+
+ verify("\nthis.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
+ { 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)\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("this.nested.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
+ { name: "bar", chain: [], loc: [[1, 17], [1, 20]] });
+
+ verify("\nthis.nested.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("this.nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
+ { 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)\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ verify("nested.this.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
+ { name: "bar", chain: [], loc: [[1, 17], [1, 20]] });
+
+ verify("\nnested.this.foo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: [], loc: [[5, 0], [5, 3]] });
+
+ verify("nested.this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
+ { 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)\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] });
+
+ // New/CallExpression + VariableDeclarator + AssignmentExpression + ObjectExpression
+
+ verify("let target=foo({bar:()=>{}})", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["target"], loc: [[1, 16], [1, 19]] });
+
+ verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n(\n)\n=>\n{\n}\n}\n)\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
+ { 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)\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ verify("let target=nested.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
+ { 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)\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
+ { 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)\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ verify("let target=this.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
+ { 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)\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
+ { 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)\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ verify("let target=this.nested.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
+ { 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)\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=this.nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
+ { 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)\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ verify("let target=nested.this.foo({bar:()=>{}})", e => e.type == "ArrowExpression",
+ { 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)\n", e => e.type == "ArrowExpression",
+ { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] });
+
+ verify("let target=nested.this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowExpression",
+ { 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)\n", e => e.type == "ArrowExpression",
+ { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] });
+
+ finish();
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_parser-10.js b/toolkit/devtools/debugger/test/browser_dbg_parser-10.js
new file mode 100644
index 000000000..af44ebdf6
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_parser-10.js
@@ -0,0 +1,127 @@
+/* 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:///modules/devtools/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/toolkit/devtools/debugger/test/browser_dbg_pause-exceptions-01.js b/toolkit/devtools/debugger/test/browser_dbg_pause-exceptions-01.js
new file mode 100644
index 000000000..e894050e3
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pause-exceptions-01.js
@@ -0,0 +1,240 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gFrames, gVariables, gPrefs, gOptions;
+
+function test() {
+ requestLongerTimeout(2);
+ initDebugger(TAB_URL).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, 3,
+ "Should have three 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;
+ });
+
+ sendMouseClickToTab(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, 3,
+ "Should have three 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, 3,
+ "Should have three 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;
+ });
+
+ sendMouseClickToTab(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/toolkit/devtools/debugger/test/browser_dbg_pause-exceptions-02.js b/toolkit/devtools/debugger/test/browser_dbg_pause-exceptions-02.js
new file mode 100644
index 000000000..aa7c03ada
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pause-exceptions-02.js
@@ -0,0 +1,196 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gFrames, gVariables, gPrefs, gOptions;
+
+function test() {
+ initDebugger(TAB_URL).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(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, 3,
+ "Should have three 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, 3,
+ "Should have three 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;
+ });
+
+ sendMouseClickToTab(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/toolkit/devtools/debugger/test/browser_dbg_pause-resume.js b/toolkit/devtools/debugger/test/browser_dbg_pause-resume.js
new file mode 100644
index 000000000..0fd898671
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pause-resume.js
@@ -0,0 +1,76 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gResumeButton, gResumeKey, gFrames;
+
+function test() {
+ initDebugger(TAB_URL).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);
+}
+
+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/toolkit/devtools/debugger/test/browser_dbg_pause-warning.js b/toolkit/devtools/debugger/test/browser_dbg_pause-warning.js
new file mode 100644
index 000000000..4da111900
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pause-warning.js
@@ -0,0 +1,98 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gTarget, gToolbox;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gTarget = gPanel.target;
+ gToolbox = gPanel._toolbox;
+
+ testPause();
+ });
+}
+
+function testPause() {
+ gDebugger.gThreadClient.addOneTimeListener("paused", () => {
+ ok(gTarget.isThreadPaused,
+ "target.isThreadPaused 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);
+}
+
+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(!gTarget.isThreadPaused,
+ "target.isThreadPaused 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/toolkit/devtools/debugger/test/browser_dbg_paused-keybindings.js b/toolkit/devtools/debugger/test/browser_dbg_paused-keybindings.js
new file mode 100644
index 000000000..8b707bddd
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_paused-keybindings.js
@@ -0,0 +1,45 @@
+/* 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 [, debuggee, panel] = yield initDebugger(TAB_URL);
+ gDebugger = panel.panelWin;
+ searchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ // Spin the event loop before causing the debuggee to pause, to allow
+ // this function to return first.
+ executeSoon(() => {
+ EventUtils.sendMouseEvent({ type: "click" },
+ debuggee.document.querySelector("button"),
+ debuggee);
+ });
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 20);
+ yield ensureThreadClientState(panel, "paused");
+
+ // Now open a tab and close it.
+ let tab2 = yield addTab(TAB_URL);
+ 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/toolkit/devtools/debugger/test/browser_dbg_pretty-print-01.js b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-01.js
new file mode 100644
index 000000000..ccec068b0
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-01.js
@@ -0,0 +1,82 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceShown(gPanel, "code_ugly.js")
+ .then(testSourceIsUgly)
+ .then(() => {
+ const finished = waitForSourceShown(gPanel, "code_ugly.js");
+ clickPrettyPrintButton();
+ testProgressBarShown();
+ return finished;
+ })
+ .then(testSourceIsPretty)
+ .then(testEditorShown)
+ .then(testSourceIsStillPretty)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
+ });
+ });
+}
+
+function testSourceIsUgly() {
+ ok(!gEditor.getText().contains("\n "),
+ "The source shouldn't be pretty printed yet.");
+}
+
+function clickPrettyPrintButton() {
+ gDebugger.document.getElementById("pretty-print").click();
+}
+
+function testProgressBarShown() {
+ const deck = gDebugger.document.getElementById("editor-deck");
+ is(deck.selectedIndex, 2, "The progress bar should be shown");
+}
+
+function testSourceIsPretty() {
+ ok(gEditor.getText().contains("\n "),
+ "The source should be pretty printed.")
+}
+
+function testEditorShown() {
+ const deck = gDebugger.document.getElementById("editor-deck");
+ is(deck.selectedIndex, 0, "The editor should be shown");
+}
+
+function testSourceIsStillPretty() {
+ const deferred = promise.defer();
+
+ const { source } = gSources.selectedItem.attachment;
+ gDebugger.DebuggerController.SourceScripts.getText(source).then(([, text]) => {
+ ok(text.contains("\n "),
+ "Subsequent calls to getText return the pretty printed source.");
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_pretty-print-02.js b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-02.js
new file mode 100644
index 000000000..26c938347
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-02.js
@@ -0,0 +1,55 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gContextMenu;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gContextMenu = gDebugger.document.getElementById("sourceEditorContextMenu");
+
+ waitForSourceShown(gPanel, "code_ugly.js")
+ .then(() => {
+ const finished = waitForSourceShown(gPanel, "code_ugly.js");
+ selectContextMenuItem();
+ return finished;
+ })
+ .then(testSourceIsPretty)
+ .then(closeDebuggerAndFinish.bind(null, gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
+ });
+ });
+}
+
+function selectContextMenuItem() {
+ once(gContextMenu, "popupshown").then(() => {
+ const menuItem = gDebugger.document.getElementById("se-dbg-cMenu-prettyPrint");
+ menuItem.click();
+ });
+ gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
+}
+
+function testSourceIsPretty() {
+ ok(gEditor.getText().contains("\n "),
+ "The source should be pretty printed.")
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gContextMenu = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_pretty-print-03.js b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-03.js
new file mode 100644
index 000000000..888fbcc02
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-03.js
@@ -0,0 +1,49 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ waitForSourceShown(gPanel, "code_ugly.js")
+ .then(runCodeAndPause)
+ .then(() => {
+ const sourceShown = waitForSourceShown(gPanel, "code_ugly.js");
+ const caretUpdated = waitForCaretUpdated(gPanel, 7);
+ const finished = promise.all([sourceShown, caretUpdated]);
+ clickPrettyPrintButton();
+ return finished;
+ })
+ .then(resumeDebuggerThenCloseAndFinish.bind(null, gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
+ });
+ });
+}
+
+function runCodeAndPause() {
+ const deferred = promise.defer();
+ once(gDebugger.gThreadClient, "paused").then(deferred.resolve);
+ callInTab(gTab, "foo");
+ return deferred.promise;
+}
+
+function clickPrettyPrintButton() {
+ gDebugger.document.getElementById("pretty-print").click();
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_pretty-print-04.js b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-04.js
new file mode 100644
index 000000000..f0a19d671
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-04.js
@@ -0,0 +1,70 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gSearchBox;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ waitForSourceShown(gPanel, "code_ugly.js")
+ .then(testUglySearch)
+ .then(() => {
+ const finished = waitForSourceShown(gPanel, "code_ugly.js");
+ clickPrettyPrintButton();
+ return finished;
+ })
+ .then(testPrettyPrintedSearch)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testUglySearch() {
+ const deferred = promise.defer();
+
+ once(gDebugger, "popupshown").then(() => {
+ ok(isCaretPos(gPanel, 2, 10),
+ "The bar function's non-pretty-printed location should be shown.");
+ deferred.resolve();
+ });
+
+ setText(gSearchBox, "@bar");
+ return deferred.promise;
+}
+
+function clickPrettyPrintButton() {
+ gDebugger.document.getElementById("pretty-print").click();
+}
+
+function testPrettyPrintedSearch() {
+ const deferred = promise.defer();
+
+ once(gDebugger, "popupshown").then(() => {
+ ok(isCaretPos(gPanel, 6, 10),
+ "The bar function's pretty printed location should be shown.");
+ deferred.resolve();
+ });
+
+ setText(gSearchBox, "@bar");
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gSearchBox = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_pretty-print-05.js b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-05.js
new file mode 100644
index 000000000..8bbaafdff
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-05.js
@@ -0,0 +1,78 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gControllerSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gControllerSources = gDebugger.DebuggerController.SourceScripts;
+
+ Task.spawn(function() {
+ yield waitForSourceShown(gPanel, TAB_URL);
+
+ // 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().contains("myFunction"),
+ "The source shouldn't be pretty printed yet.");
+
+ clickPrettyPrintButton();
+
+ let { source } = gSources.selectedItem.attachment;
+ try {
+ yield gControllerSources.togglePrettyPrint(source);
+ ok(false, "The promise for a prettified source should be rejected!");
+ } catch ([source, error]) {
+ is(error, "Can't prettify non-javascript files.",
+ "The promise was correctly rejected with a meaningful message.");
+ }
+
+ let text;
+ [source, text] = yield gControllerSources.getText(source);
+ is(getSelectedSourceURL(gSources), TAB_URL,
+ "The correct source is still selected.");
+ ok(gEditor.getText().contains("myFunction"),
+ "The displayed source hasn't changed.");
+ ok(text.contains("myFunction"),
+ "The cached source text wasn't altered in any way.");
+
+ yield closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
+
+function clickPrettyPrintButton() {
+ gDebugger.document.getElementById("pretty-print").click();
+}
+
+function prepareDebugger(aPanel) {
+ aPanel._view.Sources.preferredSource = getSourceActor(
+ aPanel.panelWin.DebuggerView.Sources,
+ TAB_URL
+ );
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gControllerSources = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_pretty-print-06.js b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-06.js
new file mode 100644
index 000000000..48f866935
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-06.js
@@ -0,0 +1,92 @@
+/* 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";
+
+let gTab, gPanel, gDebugger, gClient;
+let gEditor, gSources, gControllerSources, gPrettyPrinted;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gClient = gDebugger.gClient;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gControllerSources = gDebugger.DebuggerController.SourceScripts;
+
+ // 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 executeSoon(() => aCallback({ error: "prettyPrintError" }));
+ }
+ return aOriginalRequestMethod(aPacket, aCallback);
+ };
+ }(gClient.request));
+
+ Task.spawn(function() {
+ yield waitForSourceShown(gPanel, JS_URL);
+
+ // 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().contains("myFunction"),
+ "The source shouldn't be pretty printed yet.");
+
+ clickPrettyPrintButton();
+
+ let { source } = gSources.selectedItem.attachment;
+ try {
+ yield gControllerSources.togglePrettyPrint(source);
+ ok(false, "The promise for a prettified source should be rejected!");
+ } catch ([source, error]) {
+ ok(error.contains("prettyPrintError"),
+ "The promise was correctly rejected with a meaningful message.");
+ }
+
+ let text;
+ [source, text] = yield gControllerSources.getText(source);
+ is(getSelectedSourceURL(gSources), JS_URL,
+ "The correct source is still selected.");
+ ok(gEditor.getText().contains("myFunction"),
+ "The displayed source hasn't changed.");
+ ok(text.contains("myFunction"),
+ "The cached source text wasn't altered in any way.");
+
+ is(gPrettyPrinted, true,
+ "The hijacked pretty print method was executed.");
+
+ yield closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
+
+function clickPrettyPrintButton() {
+ gDebugger.document.getElementById("pretty-print").click();
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gClient = null;
+ gEditor = null;
+ gSources = null;
+ gControllerSources = null;
+ gPrettyPrinted = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_pretty-print-07.js b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-07.js
new file mode 100644
index 000000000..718d23a81
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-07.js
@@ -0,0 +1,57 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* 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.
+
+let gTab, gPanel, gClient, gThreadClient, gSource;
+
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print-2.html";
+
+function test() {
+ initDebugger(TAB_URL).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.contains('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.contains("\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.contains("\n "),
+ "Source should not be pretty after disabling pretty-printing");
+ closeDebuggerAndFinish(gPanel);
+}
+
+registerCleanupFunction(function() {
+ gTab = gPanel = gClient = gThreadClient = gSource = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_pretty-print-08.js b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-08.js
new file mode 100644
index 000000000..50c1f1c9b
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-08.js
@@ -0,0 +1,94 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test stepping through pretty printed sources.
+
+let gTab, gPanel, gClient, gThreadClient, gSource;
+
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print-2.html";
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gClient = gPanel.panelWin.gClient;
+ gThreadClient = gPanel.panelWin.DebuggerController.activeThread;
+
+ findSource();
+ });
+}
+
+const BP_LOCATION = {
+ line: 5,
+ column: 11
+};
+
+function findSource() {
+ gThreadClient.getSources(({ error, sources }) => {
+ ok(!error, "error should exist");
+ sources = sources.filter(s => s.url.contains("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/toolkit/devtools/debugger/test/browser_dbg_pretty-print-09.js b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-09.js
new file mode 100644
index 000000000..fdc5a779c
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-09.js
@@ -0,0 +1,87 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* 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;
+
+let gTab, gPanel, gClient, gThreadClient, gSource;
+
+const TAB_URL = EXAMPLE_URL + "doc_pretty-print-2.html";
+
+function test() {
+ initDebugger(TAB_URL).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.contains("\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/toolkit/devtools/debugger/test/browser_dbg_pretty-print-10.js b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-10.js
new file mode 100644
index 000000000..63d32b671
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-10.js
@@ -0,0 +1,61 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceShown(gPanel, "code_ugly.js")
+ .then(testSourceIsUgly)
+ .then(toggleBlackBoxing.bind(null, gPanel))
+ .then(clickPrettyPrintButton)
+ .then(testSourceIsStillUgly)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
+ });
+ });
+}
+
+function testSourceIsUgly() {
+ ok(!gEditor.getText().contains("\n "),
+ "The source shouldn't be pretty printed yet.");
+}
+
+function clickPrettyPrintButton() {
+ // Wait a tick before clicking to make sure the frontend's blackboxchange
+ // handlers have finished.
+ return new Promise(resolve => {
+ gDebugger.document.getElementById("pretty-print").click();
+ resolve();
+ });
+}
+
+function testSourceIsStillUgly() {
+ const { source } = gSources.selectedItem.attachment;
+ return gDebugger.DebuggerController.SourceScripts.getText(source).then(([, text]) => {
+ ok(!text.contains("\n "));
+ });
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_pretty-print-11.js b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-11.js
new file mode 100644
index 000000000..bced65ea8
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-11.js
@@ -0,0 +1,58 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceShown(gPanel, "code_ugly.js")
+ .then(testSourceIsUgly)
+ .then(() => {
+ const finished = waitForSourceShown(gPanel, "code_ugly.js");
+ clickPrettyPrintButton();
+ return finished;
+ })
+ .then(testSourceIsPretty)
+ .then(reloadActiveTab.bind(null, gPanel, gDebugger.EVENTS.SOURCE_SHOWN))
+ .then(testSourceIsPretty)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
+ });
+ });
+}
+
+function testSourceIsUgly() {
+ ok(!gEditor.getText().contains("\n "),
+ "The source shouldn't be pretty printed yet.");
+}
+
+function clickPrettyPrintButton() {
+ gDebugger.document.getElementById("pretty-print").click();
+}
+
+function testSourceIsPretty() {
+ ok(gEditor.getText().contains("\n "),
+ "The source should be pretty printed.")
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_pretty-print-12.js b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-12.js
new file mode 100644
index 000000000..6e56640d6
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-12.js
@@ -0,0 +1,52 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceShown(gPanel, "")
+ .then(() => {
+ let shown = ensureSourceIs(gPanel, TAB_URL, true);
+ gSources.selectedValue = getSourceActor(gSources, TAB_URL);
+ return shown;
+ })
+ .then(clickPrettyPrintButton)
+ .then(testButtonIsntChecked)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
+ });
+ });
+}
+
+function clickPrettyPrintButton() {
+ gDebugger.document.getElementById("pretty-print").click();
+}
+
+function testButtonIsntChecked() {
+ is(gDebugger.document.getElementById("pretty-print").checked, false,
+ "The button shouldn't be checked after trying to pretty print a non-js file.");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_pretty-print-13.js b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-13.js
new file mode 100644
index 000000000..c7901befe
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-13.js
@@ -0,0 +1,85 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ promise.all([waitForSourceShown(gPanel, "code_ugly-8"),
+ waitForEditorLocationSet(gPanel)])
+ .then(testSourceIsUgly)
+ .then(() => {
+ const finished = waitForSourceShown(gPanel, "code_ugly-8");
+ clickPrettyPrintButton();
+ testProgressBarShown();
+ return finished;
+ })
+ .then(testSourceIsPretty)
+ .then(testEditorShown)
+ .then(testSourceIsStillPretty)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
+ });
+ });
+}
+
+function testSourceIsUgly() {
+ ok(!gEditor.getText().contains("\n "),
+ "The source shouldn't be pretty printed yet.");
+}
+
+function clickPrettyPrintButton() {
+ gDebugger.document.getElementById("pretty-print").click();
+}
+
+function testProgressBarShown() {
+ const deck = gDebugger.document.getElementById("editor-deck");
+ is(deck.selectedIndex, 2, "The progress bar should be shown");
+}
+
+function testSourceIsPretty() {
+ ok(gEditor.getText().contains("\n "),
+ "The source should be pretty printed.")
+}
+
+function testEditorShown() {
+ const deck = gDebugger.document.getElementById("editor-deck");
+ is(deck.selectedIndex, 0, "The editor should be shown");
+}
+
+function testSourceIsStillPretty() {
+ const deferred = promise.defer();
+
+ const { source } = gSources.selectedItem.attachment;
+ gDebugger.DebuggerController.SourceScripts.getText(source).then(([, text]) => {
+ ok(text.contains("\n "),
+ "Subsequent calls to getText return the pretty printed source.");
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_pretty-print-on-paused.js b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-on-paused.js
new file mode 100644
index 000000000..faba77e5c
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_pretty-print-on-paused.js
@@ -0,0 +1,64 @@
+/* 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";
+
+let gTab, gPanel, gDebugger, gThreadClient, gSources;
+
+const SECOND_SOURCE_VALUE = EXAMPLE_URL + "code_ugly-2.js";
+
+function test(){
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gThreadClient = gDebugger.gThreadClient;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ Task.spawn(function* () {
+ try {
+ yield ensureSourceIs(gPanel, "code_script-switching-02.js", true);
+
+ yield doInterrupt(gPanel);
+
+ let source = gThreadClient.source(getSourceForm(gSources, SECOND_SOURCE_VALUE));
+ yield rdpInvoke(source, 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/toolkit/devtools/debugger/test/browser_dbg_progress-listener-bug.js b/toolkit/devtools/debugger/test/browser_dbg_progress-listener-bug.js
new file mode 100644
index 000000000..04aace77f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_progress-listener-bug.js
@@ -0,0 +1,85 @@
+/* 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).
+ */
+
+let gTab, gPanel, gDebugger;
+let gOldListener;
+
+const TAB_URL = EXAMPLE_URL + "doc_inline-script.html";
+
+function test() {
+ installListener();
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ is(!!gDebugger.DebuggerController._startup, true,
+ "Controller should be initialized after starting the test.");
+ is(!!gDebugger.DebuggerView._startup, true,
+ "View should be initialized after starting the test.");
+
+ testPause();
+ });
+}
+
+function testPause() {
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 16).then(() => {
+ is(gDebugger.gThreadClient.state, "paused",
+ "The debugger statement was reached.");
+
+ resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+
+ callInTab(gTab, "runDebuggerStatement");
+}
+
+// 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/toolkit/devtools/debugger/test/browser_dbg_reload-preferred-script-01.js b/toolkit/devtools/debugger/test/browser_dbg_reload-preferred-script-01.js
new file mode 100644
index 000000000..c1a889bfb
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_reload-preferred-script-01.js
@@ -0,0 +1,50 @@
+/* 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 before any other source was shown.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
+const PREFERRED_URL = EXAMPLE_URL + "code_script-switching-02.js";
+
+let gTab, gDebuggee, gPanel, gDebugger;
+let gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+ gTab = aTab;
+ gDebuggee = aDebuggee;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceShown(gPanel, PREFERRED_URL).then(finishTest);
+ });
+}
+
+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);
+}
+
+function prepareDebugger(aPanel) {
+ let sources = aPanel._view.Sources;
+ sources.preferredSource = getSourceActor(sources, PREFERRED_URL);
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gDebuggee = null;
+ gPanel = null;
+ gDebugger = null;
+ gSources = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_reload-preferred-script-02.js b/toolkit/devtools/debugger/test/browser_dbg_reload-preferred-script-02.js
new file mode 100644
index 000000000..60fb571b5
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_reload-preferred-script-02.js
@@ -0,0 +1,44 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gSources;
+
+function test() {
+ initDebugger(TAB_URL).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/toolkit/devtools/debugger/test/browser_dbg_reload-preferred-script-03.js b/toolkit/devtools/debugger/test/browser_dbg_reload-preferred-script-03.js
new file mode 100644
index 000000000..01cd51869
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_reload-preferred-script-03.js
@@ -0,0 +1,57 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceShown(gPanel, FIRST_URL)
+ .then(() => testSource(undefined, FIRST_URL))
+ .then(() => 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/toolkit/devtools/debugger/test/browser_dbg_reload-same-script.js b/toolkit/devtools/debugger/test/browser_dbg_reload-same-script.js
new file mode 100644
index 000000000..e03505458
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_reload-same-script.js
@@ -0,0 +1,77 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gSources, gStep;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = aPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gStep = 0;
+
+ waitForSourceShown(gPanel, FIRST_URL).then(performTest);
+ });
+
+ function performTest() {
+ switch (gStep++) {
+ case 0:
+ testCurrentSource(FIRST_URL, null);
+ reload().then(performTest);
+ break;
+ case 1:
+ testCurrentSource(FIRST_URL);
+ reload().then(performTest);
+ break;
+ case 2:
+ testCurrentSource(FIRST_URL);
+ switchAndReload(SECOND_URL).then(performTest);
+ break;
+ case 3:
+ testCurrentSource(SECOND_URL);
+ reload().then(performTest);
+ break;
+ case 4:
+ testCurrentSource(SECOND_URL);
+ reload().then(performTest);
+ break;
+ case 5:
+ testCurrentSource(SECOND_URL);
+ closeDebuggerAndFinish(gPanel);
+ break;
+ }
+ }
+
+ function reload() {
+ return reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCES_ADDED);
+ }
+
+ function switchAndReload(aUrl) {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(reload);
+ gSources.selectedValue = getSourceActor(gSources, aUrl);
+ return finished;
+ }
+
+ function testCurrentSource(aUrl, aExpectedUrl = aUrl) {
+ info("Currently preferred source: '" + gSources.preferredValue + "'.");
+ info("Currently selected source: '" + gSources.selectedValue + "'.");
+
+ is(getSourceURL(gSources, gSources.preferredValue), aExpectedUrl,
+ "The preferred source url wasn't set correctly (" + gStep + ").");
+ is(getSourceURL(gSources, gSources.selectedValue), aUrl,
+ "The selected source isn't the correct one (" + gStep + ").");
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_scripts-switching-01.js b/toolkit/devtools/debugger/test/browser_dbg_scripts-switching-01.js
new file mode 100644
index 000000000..f2202bd2d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_scripts-switching-01.js
@@ -0,0 +1,192 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ ok(gDebugger.document.title.endsWith(EXAMPLE_URL + gLabel1),
+ "Title with first source is correct.");
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+ .then(testSourcesDisplay)
+ .then(testSwitchPaused1)
+ .then(testSwitchPaused2)
+ .then(testSwitchRunning)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
+
+let gLabel1 = "code_script-switching-01.js";
+let gLabel2 = "code_script-switching-02.js";
+
+function testSourcesDisplay() {
+ let deferred = promise.defer();
+
+ is(gSources.itemCount, 2,
+ "Found the expected number of sources.");
+
+ 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.");
+ 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.");
+
+ ok(getSourceActor(gSources, EXAMPLE_URL + gLabel1),
+ "First source url is incorrect.");
+ ok(getSourceActor(gSources, EXAMPLE_URL + gLabel2),
+ "Second source url is incorrect.");
+
+ ok(gSources.getItemForAttachment(e => e.label == gLabel1),
+ "First source label is incorrect.");
+ ok(gSources.getItemForAttachment(e => e.label == gLabel2),
+ "Second source label is incorrect.");
+
+ ok(gSources.selectedItem,
+ "There should be a selected item in the sources pane.");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2,
+ "The selected value is the sources pane is incorrect.");
+
+ is(gEditor.getText().search(/firstCall/), -1,
+ "The first source is not displayed.");
+ is(gEditor.getText().search(/debugger/), 166,
+ "The second source is displayed.");
+
+ ok(gDebugger.document.title.endsWith(EXAMPLE_URL + gLabel2),
+ "Title with second source is correct.");
+
+ ok(isCaretPos(gPanel, 1),
+ "Editor caret location is correct.");
+
+ // The editor's debug location takes a tick to update.
+ executeSoon(() => {
+ is(gEditor.getDebugLocation(), 5,
+ "Editor debugger location is correct.");
+ 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.");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1,
+ "The selected value is the sources pane is incorrect.");
+
+ is(gEditor.getText().search(/firstCall/), 118,
+ "The first source is displayed.");
+ is(gEditor.getText().search(/debugger/), -1,
+ "The second source is not displayed.");
+
+ // The editor's debug location takes a tick to update.
+ executeSoon(() => {
+ ok(isCaretPos(gPanel, 1),
+ "Editor caret location is correct.");
+ is(gEditor.getDebugLocation(), null,
+ "Editor debugger location is correct.");
+ ok(!gEditor.hasLineClass(5, "debug-line"),
+ "The debugged line highlight was removed.");
+
+ 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.");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2,
+ "The selected value is the sources pane is incorrect.");
+
+ is(gEditor.getText().search(/firstCall/), -1,
+ "The first source is not displayed.");
+ is(gEditor.getText().search(/debugger/), 166,
+ "The second source is displayed.");
+
+ // The editor's debug location takes a tick to update.
+ executeSoon(() => {
+ ok(isCaretPos(gPanel, 6),
+ "Editor caret location is correct.");
+ is(gEditor.getDebugLocation(), 5,
+ "Editor debugger location is correct.");
+ ok(gEditor.hasLineClass(5, "debug-line"),
+ "The debugged line is highlighted appropriately (2).");
+
+ // 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() {
+ let deferred = promise.defer();
+
+ ok(gSources.selectedItem,
+ "There should be a selected item in the sources pane.");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1,
+ "The selected value is the sources pane is incorrect.");
+
+ is(gEditor.getText().search(/firstCall/), 118,
+ "The first source is displayed.");
+ is(gEditor.getText().search(/debugger/), -1,
+ "The second source is not displayed.");
+
+ // The editor's debug location takes a tick to update.
+ executeSoon(() => {
+ ok(isCaretPos(gPanel, 5),
+ "Editor caret location is correct.");
+ is(gEditor.getDebugLocation(), 4,
+ "Editor debugger location is correct.");
+ ok(gEditor.hasLineClass(4, "debug-line"),
+ "The debugged line is highlighted appropriately (3).");
+
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gLabel1 = null;
+ gLabel2 = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_scripts-switching-02.js b/toolkit/devtools/debugger/test/browser_dbg_scripts-switching-02.js
new file mode 100644
index 000000000..a1cdade16
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_scripts-switching-02.js
@@ -0,0 +1,182 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+ .then(testSourcesDisplay)
+ .then(testSwitchPaused1)
+ .then(testSwitchPaused2)
+ .then(testSwitchRunning)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
+
+let gLabel1 = "code_script-switching-01.js";
+let gLabel2 = "code_script-switching-02.js";
+let gParams = "?foo=bar,baz|lol";
+
+function testSourcesDisplay() {
+ let deferred = promise.defer();
+
+ is(gSources.itemCount, 2,
+ "Found the expected number of sources.");
+
+ ok(getSourceActor(gSources, EXAMPLE_URL + gLabel1),
+ "First source url is incorrect.");
+ ok(getSourceActor(gSources, EXAMPLE_URL + gLabel2 + gParams),
+ "Second source url is incorrect.");
+
+ ok(gSources.getItemForAttachment(e => e.label == gLabel1),
+ "First source label is incorrect.");
+ ok(gSources.getItemForAttachment(e => e.label == gLabel2),
+ "Second source label is incorrect.");
+
+ ok(gSources.selectedItem,
+ "There should be a selected item in the sources pane.");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2 + gParams,
+ "The selected value is the sources pane is incorrect.");
+
+ is(gEditor.getText().search(/firstCall/), -1,
+ "The first source is not displayed.");
+ is(gEditor.getText().search(/debugger/), 166,
+ "The second source is displayed.");
+
+ ok(isCaretPos(gPanel, 1),
+ "Editor caret location is correct.");
+
+ // The editor's debug location takes a tick to update.
+ executeSoon(() => {
+ is(gEditor.getDebugLocation(), 5,
+ "Editor debugger location is correct.");
+ ok(gEditor.hasLineClass(5, "debug-line"),
+ "The debugged line is highlighted appropriately.");
+
+ 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.");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1,
+ "The selected value is the sources pane is incorrect.");
+
+ is(gEditor.getText().search(/firstCall/), 118,
+ "The first source is displayed.");
+ is(gEditor.getText().search(/debugger/), -1,
+ "The second source is not displayed.");
+
+ // The editor's debug location takes a tick to update.
+ executeSoon(() => {
+ ok(isCaretPos(gPanel, 1),
+ "Editor caret location is correct.");
+
+ is(gEditor.getDebugLocation(), null,
+ "Editor debugger location is correct.");
+ ok(!gEditor.hasLineClass(5, "debug-line"),
+ "The debugged line highlight was removed.");
+
+ 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.");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2 + gParams,
+ "The selected value is the sources pane is incorrect.");
+
+ is(gEditor.getText().search(/firstCall/), -1,
+ "The first source is not displayed.");
+ is(gEditor.getText().search(/debugger/), 166,
+ "The second source is displayed.");
+
+ // The editor's debug location takes a tick to update.
+ executeSoon(() => {
+ ok(isCaretPos(gPanel, 6),
+ "Editor caret location is correct.");
+ is(gEditor.getDebugLocation(), 5,
+ "Editor debugger location is correct.");
+ ok(gEditor.hasLineClass(5, "debug-line"),
+ "The debugged line is highlighted appropriately.");
+
+ // 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.");
+ is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1,
+ "The selected value is the sources pane is incorrect.");
+
+ is(gEditor.getText().search(/firstCall/), 118,
+ "The first source is displayed.");
+ is(gEditor.getText().search(/debugger/), -1,
+ "The second source is not displayed.");
+
+ // The editor's debug location takes a tick to update.
+ executeSoon(() => {
+ ok(isCaretPos(gPanel, 5),
+ "Editor caret location is correct.");
+ is(gEditor.getDebugLocation(), 4,
+ "Editor debugger location is correct.");
+ ok(gEditor.hasLineClass(4, "debug-line"),
+ "The debugged line is highlighted appropriately.");
+
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gLabel1 = null;
+ gLabel2 = null;
+ gParams = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_scripts-switching-03.js b/toolkit/devtools/debugger/test/browser_dbg_scripts-switching-03.js
new file mode 100644
index 000000000..ddc34428a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_scripts-switching-03.js
@@ -0,0 +1,50 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gView, gEditor, gL10N;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gView = gDebugger.DebuggerView;
+ gEditor = gDebugger.DebuggerView.editor;
+ gL10N = gDebugger.L10N;
+
+ waitForSourceShown(gPanel, "-01.js")
+ .then(showBogusSource)
+ .then(testDebuggerLoadingError)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function showBogusSource() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_ERROR_SHOWN);
+ gView._setEditorSource({ url: "http://example.com/fake.js", actor: "fake.actor" });
+ return finished;
+}
+
+function testDebuggerLoadingError() {
+ ok(gEditor.getText().contains(gL10N.getStr("errorLoadingText")),
+ "The valid error loading message is displayed.");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gView = null;
+ gEditor = null;
+ gL10N = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_search-autofill-identifier.js b/toolkit/devtools/debugger/test/browser_dbg_search-autofill-identifier.js
new file mode 100644
index 000000000..60c184994
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-autofill-identifier.js
@@ -0,0 +1,135 @@
+/* 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";
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ let Source = 'code_function-search-01.js';
+ let Debugger = aPanel.panelWin;
+ let Editor = Debugger.DebuggerView.editor;
+ let Filtering = Debugger.DebuggerView.Filtering;
+
+ function doSearch(aOperator) {
+ Editor.dropSelection();
+ Filtering._doSearch(aOperator);
+ }
+
+ waitForSourceShown(aPanel, Source).then(() => {
+ 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/toolkit/devtools/debugger/test/browser_dbg_search-basic-01.js b/toolkit/devtools/debugger/test/browser_dbg_search-basic-01.js
new file mode 100644
index 000000000..5a746a5b3
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-basic-01.js
@@ -0,0 +1,317 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gFiltering, gSearchBox;
+
+function test() {
+ initDebugger(TAB_URL).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;
+
+ waitForSourceShown(gPanel, ".html").then(performTest);
+ });
+}
+
+function performTest() {
+ 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/toolkit/devtools/debugger/test/browser_dbg_search-basic-02.js b/toolkit/devtools/debugger/test/browser_dbg_search-basic-02.js
new file mode 100644
index 000000000..c933b6f3a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-basic-02.js
@@ -0,0 +1,122 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gSources, gSearchBox;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+ .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, 1),
+ 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.contains(aUrl),
+ "The selected item's label appears to be correct.");
+ ok(gSources.selectedItem.attachment.source.url.contains(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/toolkit/devtools/debugger/test/browser_dbg_search-basic-03.js b/toolkit/devtools/debugger/test/browser_dbg_search-basic-03.js
new file mode 100644
index 000000000..74020e8e4
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-basic-03.js
@@ -0,0 +1,118 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gSources, gSearchBox;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
+ .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, 1),
+ 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.contains(aUrl),
+ "The selected item's label appears to be correct.");
+ ok(gSources.selectedItem.attachment.source.url.contains(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/toolkit/devtools/debugger/test/browser_dbg_search-basic-04.js b/toolkit/devtools/debugger/test/browser_dbg_search-basic-04.js
new file mode 100644
index 000000000..be115654b
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-basic-04.js
@@ -0,0 +1,130 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gSearchBox;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ waitForSourceShown(gPanel, "-01.js")
+ .then(testLineSearch)
+ .then(testTokenSearch)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+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/toolkit/devtools/debugger/test/browser_dbg_search-global-01.js b/toolkit/devtools/debugger/test/browser_dbg_search-global-01.js
new file mode 100644
index 000000000..265ff92f6
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-global-01.js
@@ -0,0 +1,272 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gSearchView, gSearchBox;
+
+function test() {
+ initDebugger(TAB_URL).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).contains("-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).contains("-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/toolkit/devtools/debugger/test/browser_dbg_search-global-02.js b/toolkit/devtools/debugger/test/browser_dbg_search-global-02.js
new file mode 100644
index 000000000..64d6f3c46
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-global-02.js
@@ -0,0 +1,217 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gSearchView, gSearchBox;
+
+function test() {
+ initDebugger(TAB_URL).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(doFirstJump)
+ .then(doSecondJump)
+ .then(doWrapAroundJump)
+ .then(doBackwardsWrapAroundJump)
+ .then(testSearchTokenEmpty)
+ .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).contains("-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).contains("-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.
+ executeSoon(() => {
+ 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).contains("-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).");
+
+ // The editor's selected text takes a tick to update.
+ executeSoon(() => {
+ 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).contains("-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.
+ executeSoon(() => {
+ 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).contains("-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.
+ executeSoon(() => {
+ 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).contains("-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.");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gSearchView = null;
+ gSearchBox = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_search-global-03.js b/toolkit/devtools/debugger/test/browser_dbg_search-global-03.js
new file mode 100644
index 000000000..5613f661b
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-global-03.js
@@ -0,0 +1,104 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gSearchView, gSearchBox;
+
+function test() {
+ initDebugger(TAB_URL).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).contains("-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/toolkit/devtools/debugger/test/browser_dbg_search-global-04.js b/toolkit/devtools/debugger/test/browser_dbg_search-global-04.js
new file mode 100644
index 000000000..b22065bc8
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-global-04.js
@@ -0,0 +1,92 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gSearchView, gSearchBox;
+
+function test() {
+ initDebugger(TAB_URL).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).contains("-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).contains("-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/toolkit/devtools/debugger/test/browser_dbg_search-global-05.js b/toolkit/devtools/debugger/test/browser_dbg_search-global-05.js
new file mode 100644
index 000000000..c408e49c1
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-global-05.js
@@ -0,0 +1,154 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gSearchView, gSearchBox;
+
+function test() {
+ initDebugger(TAB_URL).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.");
+ ok(getSelectedSourceURL(gSources).contains("-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 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.")
+ is(item1.instance.expanded, false,
+ "The second source results should be collapsed on click.")
+
+ 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.");
+ is(item1.instance.expanded, true,
+ "The second source results should be expanded on an additional click.");
+}
+
+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, 1),
+ "The editor didn't jump to the correct line (1).");
+ is(gEditor.getSelection(), "",
+ "The editor didn't select the correct text (1).");
+ ok(getSelectedSourceURL(gSources).contains("-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).");
+
+ 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", 1, 1).then(() => {
+ info("Current source url:\n" + getSelectedSourceURL(gSources));
+ info("Debugger editor text:\n" + gEditor.getText());
+
+ ok(isCaretPos(gPanel, 1, 1),
+ "The editor didn't jump to the correct line (2).");
+ is(gEditor.getSelection(), "",
+ "The editor didn't select the correct text (2).");
+ ok(getSelectedSourceURL(gSources).contains("-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).");
+
+ 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/toolkit/devtools/debugger/test/browser_dbg_search-global-06.js b/toolkit/devtools/debugger/test/browser_dbg_search-global-06.js
new file mode 100644
index 000000000..578120a08
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-global-06.js
@@ -0,0 +1,119 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gSearchView, gSearchBox;
+
+function test() {
+ initDebugger(TAB_URL).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).contains("-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/toolkit/devtools/debugger/test/browser_dbg_search-popup-jank.js b/toolkit/devtools/debugger/test/browser_dbg_search-popup-jank.js
new file mode 100644
index 000000000..e00d28933
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-popup-jank.js
@@ -0,0 +1,117 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gSearchBox;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ gDebugger.DebuggerView.FilteredSources._autoSelectFirstItem = false;
+ gDebugger.DebuggerView.FilteredFunctions._autoSelectFirstItem = false;
+
+ waitForSourceShown(gPanel, "-01.js")
+ .then(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(() => pressKey("UP"))
+ .then(() => ensureSourceIs(aPanel, "doc_editor-mode", true))
+ .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(() => typeText(gSearchBox, ":"))
+ .then(() => waitForSourceShown(gPanel, "code_test-editor-mode"))
+ .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/toolkit/devtools/debugger/test/browser_dbg_search-sources-01.js b/toolkit/devtools/debugger/test/browser_dbg_search-sources-01.js
new file mode 100644
index 000000000..cc2b6ff3a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-sources-01.js
@@ -0,0 +1,233 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gSources, gSearchView, gSearchBox;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(3);
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchView = gDebugger.DebuggerView.FilteredSources;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ waitForSourceShown(gPanel, "-01.js")
+ .then(bogusSearch)
+ .then(firstSearch)
+ .then(secondSearch)
+ .then(thirdSearch)
+ .then(fourthSearch)
+ .then(fifthSearch)
+ .then(sixthSearch)
+ .then(seventhSearch)
+ .then(() => 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, 1),
+ 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.");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gSources = null;
+ gSearchView = null;
+ gSearchBox = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_search-sources-02.js b/toolkit/devtools/debugger/test/browser_dbg_search-sources-02.js
new file mode 100644
index 000000000..e75f76726
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-sources-02.js
@@ -0,0 +1,276 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gSources, gSourceUtils, gSearchView, gSearchBox;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(3);
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSourceUtils = gDebugger.SourceUtils;
+ gSearchView = gDebugger.DebuggerView.FilteredSources;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ waitForSourceShown(gPanel, "-01.js")
+ .then(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/toolkit/devtools/debugger/test/browser_dbg_search-sources-03.js b/toolkit/devtools/debugger/test/browser_dbg_search-sources-03.js
new file mode 100644
index 000000000..e783b9e8b
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-sources-03.js
@@ -0,0 +1,98 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gSources, gSearchBox;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
+
+ waitForSourceShown(gPanel, "-01.js")
+ .then(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/toolkit/devtools/debugger/test/browser_dbg_search-symbols.js b/toolkit/devtools/debugger/test/browser_dbg_search-symbols.js
new file mode 100644
index 000000000..803e51ef5
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_search-symbols.js
@@ -0,0 +1,467 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gSearchBox, gFilteredFunctions;
+
+function test() {
+ initDebugger(TAB_URL).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.FilteredFunctions;
+
+ waitForSourceShown(gPanel, "-01.js")
+ .then(() => 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 corect 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").contains(value),
+ "The corect 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 corect 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 corect 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").contains(value),
+ "The corect 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 corect 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 corect 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").contains(value),
+ "The corect 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 corect 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 corect 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").contains(value),
+ "The corect 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 corect 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 corect 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").contains(value),
+ "The corect 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 corect 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/toolkit/devtools/debugger/test/browser_dbg_searchbox-help-popup-01.js b/toolkit/devtools/debugger/test/browser_dbg_searchbox-help-popup-01.js
new file mode 100644
index 000000000..29e4848f0
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_searchbox-help-popup-01.js
@@ -0,0 +1,58 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gSearchBox, gSearchBoxPanel;
+
+function test() {
+ initDebugger(TAB_URL).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/toolkit/devtools/debugger/test/browser_dbg_searchbox-help-popup-02.js b/toolkit/devtools/debugger/test/browser_dbg_searchbox-help-popup-02.js
new file mode 100644
index 000000000..f71951bc7
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_searchbox-help-popup-02.js
@@ -0,0 +1,84 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSearchBox, gSearchBoxPanel;
+
+function test() {
+ initDebugger(TAB_URL).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, 6, 1),
+ "The editor caret position appears to be correct after gaining focus.");
+ ok(isEditorSel(gPanel, [165, 165]),
+ "The editor selection appears to be correct after gaining focus.");
+ is(gEditor.getSelection(), "",
+ "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/toolkit/devtools/debugger/test/browser_dbg_searchbox-parse.js b/toolkit/devtools/debugger/test/browser_dbg_searchbox-parse.js
new file mode 100644
index 000000000..efc30144c
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_searchbox-parse.js
@@ -0,0 +1,124 @@
+/* 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("about:blank").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/toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-01.js b/toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-01.js
new file mode 100644
index 000000000..e6cf32590
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-01.js
@@ -0,0 +1,250 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 740825: 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 gTab, gPanel, gDebugger;
+ let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gBreakpointsAdded = gBreakpoints._added;
+ gBreakpointsRemoving = gBreakpoints._removing;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
+ .then(() => addBreakpoints())
+ .then(() => initialChecks())
+ .then(() => resumeAndTestBreakpoint(20))
+ .then(() => resumeAndTestBreakpoint(21))
+ .then(() => resumeAndTestBreakpoint(22))
+ .then(() => resumeAndTestBreakpoint(23))
+ .then(() => resumeAndTestBreakpoint(24))
+ .then(() => resumeAndTestBreakpoint(25))
+ .then(() => resumeAndTestBreakpoint(27))
+ .then(() => resumeAndTestBreakpoint(28))
+ .then(() => resumeAndTestBreakpoint(29))
+ .then(() => resumeAndTestNoBreakpoint())
+ .then(() => {
+ return promise.all([
+ reloadActiveTab(gPanel, gDebugger.EVENTS.BREAKPOINT_SHOWN_IN_EDITOR, 13),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_SHOWN_IN_PANE, 13)
+ ]);
+ })
+ .then(() => testAfterReload())
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "ermahgerd");
+ });
+
+ function addBreakpoints() {
+ return promise.resolve(null)
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
+ line: 18,
+ condition: "undefined"
+ }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
+ line: 19,
+ condition: "null"
+ }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
+ line: 20,
+ condition: "42"
+ }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
+ line: 21,
+ condition: "true"
+ }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
+ line: 22,
+ condition: "'nasu'"
+ }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
+ line: 23,
+ condition: "/regexp/"
+ }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
+ line: 24,
+ condition: "({})"
+ }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
+ line: 25,
+ condition: "(function() {})"
+ }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
+ line: 26,
+ condition: "(function() { return false; })()"
+ }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
+ line: 27,
+ condition: "a"
+ }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
+ line: 28,
+ condition: "a !== undefined"
+ }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
+ line: 29,
+ condition: "a !== null"
+ }))
+ .then(() => gPanel.addBreakpoint({ actor: gSources.selectedValue,
+ line: 30,
+ condition: "b"
+ }));
+ }
+
+ function initialChecks() {
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ 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.");
+
+ is(gBreakpointsAdded.size, 13,
+ "13 breakpoints currently added.");
+ is(gBreakpointsRemoving.size, 0,
+ "No breakpoints currently being removed.");
+ is(gEditor.getBreakpoints().length, 13,
+ "13 breakpoints currently shown in the editor.");
+
+ ok(!gBreakpoints._getAdded({ url: "foo", line: 3 }),
+ "_getAdded('foo', 3) returns falsey.");
+ ok(!gBreakpoints._getRemoving({ url: "bar", line: 3 }),
+ "_getRemoving('bar', 3) returns falsey.");
+ }
+
+ function resumeAndTestBreakpoint(aLine) {
+ let finished = waitForCaretUpdated(gPanel, aLine).then(() => testBreakpoint(aLine));
+
+ 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._selectedBreakpointItem,
+ "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(aLine, aHighlightBreakpoint) {
+ // Highlight the breakpoint only if required.
+ if (aHighlightBreakpoint) {
+ let finished = waitForCaretUpdated(gPanel, aLine).then(() => testBreakpoint(aLine));
+ gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: aLine });
+ return finished;
+ }
+
+ let selectedActor = gSources.selectedValue;
+ let selectedBreakpoint = gSources._selectedBreakpointItem;
+
+ ok(selectedActor,
+ "There should be a selected item in the sources pane.");
+ ok(selectedBreakpoint,
+ "There should be a selected brekapoint in the sources pane.");
+
+ is(selectedBreakpoint.attachment.actor, selectedActor,
+ "The breakpoint on line " + aLine + " wasn't added on the correct source.");
+ is(selectedBreakpoint.attachment.line, aLine,
+ "The breakpoint on line " + aLine + " wasn't found.");
+ is(!!selectedBreakpoint.attachment.disabled, false,
+ "The breakpoint on line " + aLine + " should be enabled.");
+ is(!!selectedBreakpoint.attachment.openPopup, false,
+ "The breakpoint on line " + aLine + " should not have opened a popup.");
+ is(gSources._conditionalPopupVisible, false,
+ "The breakpoint conditional expression popup should not have been shown.");
+
+ return gBreakpoints._getAdded(selectedBreakpoint.attachment).then(aBreakpointClient => {
+ is(aBreakpointClient.location.actor, selectedActor,
+ "The breakpoint's client url is correct");
+ is(aBreakpointClient.location.line, aLine,
+ "The breakpoint's client line is correct");
+ isnot(aBreakpointClient.condition, undefined,
+ "The breakpoint on line " + aLine + " should have a conditional expression.");
+
+ ok(isCaretPos(gPanel, aLine),
+ "The editor caret position is not properly set.");
+ });
+ }
+
+ function testAfterReload() {
+ let selectedActor = getSelectedSourceURL(gSources);
+ let selectedBreakpoint = gSources._selectedBreakpointItem;
+
+ ok(selectedActor,
+ "There should be a selected item in the sources pane after reload.");
+ ok(!selectedBreakpoint,
+ "There should be no selected brekapoint in the sources pane after reload.");
+
+ return promise.resolve(null)
+ .then(() => testBreakpoint(18, true))
+ .then(() => testBreakpoint(19, true))
+ .then(() => testBreakpoint(20, true))
+ .then(() => testBreakpoint(21, true))
+ .then(() => testBreakpoint(22, true))
+ .then(() => testBreakpoint(23, true))
+ .then(() => testBreakpoint(24, true))
+ .then(() => testBreakpoint(25, true))
+ .then(() => testBreakpoint(26, true))
+ .then(() => testBreakpoint(27, true))
+ .then(() => testBreakpoint(28, true))
+ .then(() => testBreakpoint(29, true))
+ .then(() => testBreakpoint(30, true))
+ .then(() => {
+ 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._selectedBreakpointItem,
+ "There should be a selected breakpoint in the sources pane.")
+ is(gSources._conditionalPopupVisible, false,
+ "The breakpoint conditional expression popup should not be shown.");
+ });
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-02.js b/toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-02.js
new file mode 100644
index 000000000..1fad10177
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-02.js
@@ -0,0 +1,191 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 812172: Test adding and modifying conditional breakpoints (with server-side support)
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
+
+function test() {
+ let gTab, gPanel, gDebugger;
+ let gEditor, gSources, gBreakpoints, gBreakpointsAdded, gBreakpointsRemoving;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gBreakpointsAdded = gBreakpoints._added;
+ gBreakpointsRemoving = gBreakpoints._removing;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
+ .then(() => initialChecks())
+ .then(() => addBreakpoint1())
+ .then(() => testBreakpoint(18, false, false, undefined))
+ .then(() => addBreakpoint2())
+ .then(() => testBreakpoint(19, false, false, undefined))
+ .then(() => modBreakpoint2())
+ .then(() => testBreakpoint(19, false, true, undefined))
+ .then(() => addBreakpoint3())
+ .then(() => testBreakpoint(20, true, false, undefined))
+ .then(() => modBreakpoint3())
+ .then(() => testBreakpoint(20, true, false, "bamboocha"))
+ .then(() => addBreakpoint4())
+ .then(() => testBreakpoint(21, false, false, undefined))
+ .then(() => delBreakpoint4())
+ .then(() => setCaretPosition(18))
+ .then(() => testBreakpoint(18, false, false, undefined))
+ .then(() => setCaretPosition(19))
+ .then(() => testBreakpoint(19, false, false, undefined))
+ .then(() => setCaretPosition(20))
+ .then(() => testBreakpoint(20, true, false, "bamboocha"))
+ .then(() => setCaretPosition(17))
+ .then(() => testNoBreakpoint(17))
+ .then(() => setCaretPosition(21))
+ .then(() => testNoBreakpoint(21))
+ .then(() => clickOnBreakpoint(0))
+ .then(() => testBreakpoint(18, false, false, undefined))
+ .then(() => clickOnBreakpoint(1))
+ .then(() => testBreakpoint(19, false, false, undefined))
+ .then(() => clickOnBreakpoint(2))
+ .then(() => testBreakpoint(20, true, true, "bamboocha"))
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "ermahgerd");
+ });
+
+ function initialChecks() {
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ 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.");
+
+ is(gBreakpointsAdded.size, 0,
+ "No breakpoints currently added.");
+ is(gBreakpointsRemoving.size, 0,
+ "No breakpoints currently being removed.");
+ is(gEditor.getBreakpoints().length, 0,
+ "No breakpoints currently shown in the editor.");
+
+ ok(!gBreakpoints._getAdded({ actor: "foo", line: 3 }),
+ "_getAdded('foo', 3) returns falsey.");
+ ok(!gBreakpoints._getRemoving({ actor: "bar", line: 3 }),
+ "_getRemoving('bar', 3) returns falsey.");
+ }
+
+ function addBreakpoint1() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
+ gPanel.addBreakpoint({ actor: gSources.selectedValue, line: 18 });
+ return finished;
+ }
+
+ function addBreakpoint2() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
+ setCaretPosition(19);
+ gSources._onCmdAddBreakpoint();
+ return finished;
+ }
+
+ function modBreakpoint2() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
+ setCaretPosition(19);
+ gSources._onCmdAddConditionalBreakpoint();
+ return finished;
+ }
+
+ function addBreakpoint3() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
+ setCaretPosition(20);
+ gSources._onCmdAddConditionalBreakpoint();
+ return finished;
+ }
+
+ function modBreakpoint3() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_HIDING);
+ typeText(gSources._cbTextbox, "bamboocha");
+ EventUtils.sendKey("RETURN", gDebugger);
+ return finished;
+ }
+
+ function addBreakpoint4() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
+ setCaretPosition(21);
+ gSources._onCmdAddBreakpoint();
+ return finished;
+ }
+
+ function delBreakpoint4() {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
+ setCaretPosition(21);
+ gSources._onCmdAddBreakpoint();
+ return finished;
+ }
+
+ function testBreakpoint(aLine, aOpenPopupFlag, aPopupVisible, aConditionalExpression) {
+ let selectedActor = gSources.selectedValue;
+ let selectedBreakpoint = gSources._selectedBreakpointItem;
+
+ ok(selectedActor,
+ "There should be a selected item in the sources pane.");
+ ok(selectedBreakpoint,
+ "There should be a selected brekapoint in the sources pane.");
+
+ is(selectedBreakpoint.attachment.actor, selectedActor,
+ "The breakpoint on line " + aLine + " wasn't added on the correct source.");
+ is(selectedBreakpoint.attachment.line, aLine,
+ "The breakpoint on line " + aLine + " wasn't found.");
+ is(!!selectedBreakpoint.attachment.disabled, false,
+ "The breakpoint on line " + aLine + " should be enabled.");
+ is(!!selectedBreakpoint.attachment.openPopup, aOpenPopupFlag,
+ "The breakpoint on line " + aLine + " should have a correct popup state (1).");
+ is(gSources._conditionalPopupVisible, aPopupVisible,
+ "The breakpoint on line " + aLine + " should have a correct popup state (2).");
+
+ return gBreakpoints._getAdded(selectedBreakpoint.attachment).then(aBreakpointClient => {
+ is(aBreakpointClient.location.actor, selectedActor,
+ "The breakpoint's client url is correct");
+ is(aBreakpointClient.location.line, aLine,
+ "The breakpoint's client line is correct");
+ is(aBreakpointClient.condition, aConditionalExpression,
+ "The breakpoint on line " + aLine + " should have a correct conditional expression.");
+ is("condition" in aBreakpointClient, !!aConditionalExpression,
+ "The breakpoint on line " + aLine + " should have a correct conditional state.");
+
+ ok(isCaretPos(gPanel, aLine),
+ "The editor caret position is not properly set.");
+ });
+ }
+
+ function testNoBreakpoint(aLine) {
+ let selectedUrl = getSelectedSourceURL(gSources);
+ let selectedBreakpoint = gSources._selectedBreakpointItem;
+
+ ok(selectedUrl,
+ "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);
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-03.js b/toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-03.js
new file mode 100644
index 000000000..604320be6
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-03.js
@@ -0,0 +1,82 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints, gLocation;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+
+ gLocation = { actor: gSources.selectedValue, line: 18 };
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
+ .then(addBreakpoint)
+ .then(setConditional)
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
+ toggleBreakpoint();
+ return finished;
+ })
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
+ toggleBreakpoint();
+ return finished;
+ })
+ .then(testConditionalExpressionOnClient)
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
+ openConditionalPopup();
+ return finished;
+ })
+ .then(testConditionalExpressionInPopup)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "ermahgerd");
+ });
+
+ function addBreakpoint() {
+ return gPanel.addBreakpoint(gLocation);
+ }
+
+ function setConditional(aClient) {
+ return gBreakpoints.updateCondition(aClient.location, "hello");
+ }
+
+ function toggleBreakpoint() {
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelector(".dbg-breakpoint-checkbox"),
+ gDebugger);
+ }
+
+ function openConditionalPopup() {
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelector(".dbg-breakpoint"),
+ gDebugger);
+ }
+
+ function testConditionalExpressionOnClient() {
+ return gBreakpoints._getAdded(gLocation).then(aClient => {
+ is(aClient.condition, "hello", "The expression is correct (1).");
+ });
+ }
+
+ function testConditionalExpressionInPopup() {
+ let textbox = gDebugger.document.getElementById("conditional-breakpoint-panel-textbox");
+ is(textbox.value, "hello", "The expression is correct (2).")
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-04.js b/toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-04.js
new file mode 100644
index 000000000..43fc2e1b2
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_server-conditional-bp-04.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that conditional breakpoints with undefined expressions
+ * are stored as plain breakpoints when re-enabling them (with
+ * server-side support)
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html";
+
+function test() {
+ let gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints, gLocation;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+
+ gLocation = { actor: gSources.selectedValue, line: 18 };
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 17)
+ .then(addBreakpoint)
+ .then(setDummyConditional)
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_REMOVED);
+ toggleBreakpoint();
+ return finished;
+ })
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED);
+ toggleBreakpoint();
+ return finished;
+ })
+ .then(testConditionalExpressionOnClient)
+ .then(() => {
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWING);
+ openConditionalPopup();
+ finished.then(() => ok(false, "The popup shouldn't have opened."));
+ return waitForTime(1000);
+ })
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "ermahgerd");
+ });
+
+ function addBreakpoint() {
+ return gPanel.addBreakpoint(gLocation);
+ }
+
+ function setDummyConditional(aClient) {
+ // This happens when a conditional expression input popup is shown
+ // but the user doesn't type anything into it.
+ return gBreakpoints.updateCondition(aClient.location, '');
+ }
+
+ function toggleBreakpoint() {
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelector(".dbg-breakpoint-checkbox"),
+ gDebugger);
+ }
+
+ function openConditionalPopup() {
+ EventUtils.sendMouseEvent({ type: "click" },
+ gDebugger.document.querySelector(".dbg-breakpoint"),
+ gDebugger);
+ }
+
+ function testConditionalExpressionOnClient() {
+ return gBreakpoints._getAdded(gLocation).then(aClient => {
+ if ("condition" in aClient) {
+ ok(false, "A conditional expression shouldn't have been set.");
+ } else {
+ ok(true, "The conditional expression wasn't set, as expected.");
+ }
+ });
+ }
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_source-maps-01.js b/toolkit/devtools/debugger/test/browser_dbg_source-maps-01.js
new file mode 100644
index 000000000..4bbbb14be
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_source-maps-01.js
@@ -0,0 +1,167 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ checkSourceMapsEnabled();
+
+ waitForSourceShown(gPanel, ".coffee")
+ .then(checkInitialSource)
+ .then(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: 3 }, aResponse => {
+ ok(!aResponse.error,
+ "Should be able to set a breakpoint in a coffee source file on a blank line.");
+ ok(aResponse.actualLocation,
+ "Because 3 is empty, we should have an actualLocation.");
+ is(aResponse.actualLocation.source.url, COFFEE_URL,
+ "actualLocation.actor should be source mapped to the coffee file.");
+ is(aResponse.actualLocation.line, 2,
+ "actualLocation.line should be source mapped back to 2.");
+
+ 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' 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, 6).then(deferred.resolve);
+ });
+ });
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_source-maps-02.js b/toolkit/devtools/debugger/test/browser_dbg_source-maps-02.js
new file mode 100644
index 000000000..433843475
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_source-maps-02.js
@@ -0,0 +1,148 @@
+/* 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";
+
+let gTab, gPanel, gDebugger, gEditor;
+let gSources, gFrames, gPrefs, gOptions;
+
+function test() {
+ initDebugger(TAB_URL).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;
+
+ waitForSourceShown(gPanel, ".coffee")
+ .then(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/toolkit/devtools/debugger/test/browser_dbg_source-maps-03.js b/toolkit/devtools/debugger/test/browser_dbg_source-maps-03.js
new file mode 100644
index 000000000..a7a48f361
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_source-maps-03.js
@@ -0,0 +1,83 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gFrames;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+
+ waitForSourceShown(gPanel, JS_URL)
+ .then(checkInitialSource)
+ .then(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, column: 21 }, 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 and column 10.");
+
+ 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/toolkit/devtools/debugger/test/browser_dbg_source-maps-04.js b/toolkit/devtools/debugger/test/browser_dbg_source-maps-04.js
new file mode 100644
index 000000000..cc285c493
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_source-maps-04.js
@@ -0,0 +1,183 @@
+/* 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.
+let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
+DevToolsUtils.reportingDisabled = true;
+
+let gPanel, gDebugger, gFrames, gSources, gPrefs, gOptions;
+
+function test() {
+ initDebugger(TAB_URL).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.");
+
+ waitForSourceShown(gPanel, JS_URL)
+ .then(checkInitialSource)
+ .then(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: 61 }, 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 61.");
+
+ 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/toolkit/devtools/debugger/test/browser_dbg_sources-bookmarklet.js b/toolkit/devtools/debugger/test/browser_dbg_sources-bookmarklet.js
new file mode 100644
index 000000000..e4a5841a4
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_sources-bookmarklet.js
@@ -0,0 +1,50 @@
+/* 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 gTab, gPanel, gDebugger;
+ let gSources, gBreakpoints;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+
+ return Task.spawn(function*() {
+ let waitForSource = waitForDebuggerEvents(gPanel, gPanel.panelWin.EVENTS.NEW_SOURCE, 1);
+
+ // 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 waitForSource;
+
+ is(gSources.values.length, 2, "Should have 2 source");
+
+ let item = gSources.getItemForAttachment(e => {
+ return e.label.indexOf("javascript:") === 0;
+ });
+ ok(item, "Source label is incorrect.");
+
+ let res = yield promiseInvoke(gDebugger.DebuggerController.client,
+ gDebugger.DebuggerController.client.request,
+ { to: item.value, type: "source"});
+
+ ok(res && res.source == BOOKMARKLET_SCRIPT_CODE, "SourceActor reply received");
+ is(res.source, BOOKMARKLET_SCRIPT_CODE, "source is correct");
+ is(res.contentType, "text/javascript", "contentType is correct");
+
+ yield closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_sources-cache.js b/toolkit/devtools/debugger/test/browser_dbg_sources-cache.js
new file mode 100644
index 000000000..b838abf8d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_sources-cache.js
@@ -0,0 +1,142 @@
+/* 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;
+
+let gTab, gDebuggee, gPanel, gDebugger;
+let gEditor, gSources, gControllerSources;
+let gPrevLabelsCache, gPrevGroupsCache;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+ gTab = aTab;
+ gDebuggee = aDebuggee;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gEditor = gDebugger.DebuggerView.editor;
+ gSources = gDebugger.DebuggerView.Sources;
+ gControllerSources = gDebugger.DebuggerController.SourceScripts;
+ gPrevLabelsCache = gDebugger.SourceUtils._labelsCache;
+ gPrevGroupsCache = gDebugger.SourceUtils._groupsCache;
+
+ waitForSourceShown(gPanel, "-01.js")
+ .then(initialChecks)
+ .then(getTextForSourcesAndCheckIntegrity)
+ .then(performReloadAndTestState)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function initialChecks() {
+ ok(gEditor.getText().contains("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).contains("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.contains("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.contains("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.contains("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.contains("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 getTextForSourcesAndCheckIntegrity() {
+ return gControllerSources.getTextForSources(gSources.values).then(testCacheIntegrity);
+}
+
+function performReloadAndTestState() {
+ gDebugger.gTarget.once("will-navigate", testStateBeforeReload);
+ gDebugger.gTarget.once("navigate", testStateAfterReload);
+ return reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+}
+
+function testCacheIntegrity(aSources) {
+ for (let [actor, contents] of aSources) {
+ // Sources of a debugee don't always finish fetching consecutively. D'uh.
+ let index = gSources.values.indexOf(actor);
+
+ ok(index >= 0 && index <= TOTAL_SOURCES,
+ "Found a source actor cached correctly (" + index + ").");
+ ok(contents.contains(
+ ["First source!", "Second source!", "Third source!", "Peanut butter jelly time!"][index]),
+ "Found a source's text contents cached correctly (" + index + ").");
+
+ info("Cached source actor at " + index + ": " + actor);
+ info("Cached source text at " + index + ": " + contents);
+ }
+}
+
+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.");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gDebuggee = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gControllerSources = null;
+ gPrevLabelsCache = null;
+ gPrevGroupsCache = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_sources-eval-01.js b/toolkit/devtools/debugger/test/browser_dbg_sources-eval-01.js
new file mode 100644
index 000000000..c60ceb50f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_sources-eval-01.js
@@ -0,0 +1,39 @@
+/* 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;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+
+ return Task.spawn(function*() {
+ yield waitForSourceShown(gPanel, "-eval.js");
+ 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/toolkit/devtools/debugger/test/browser_dbg_sources-eval-02.js b/toolkit/devtools/debugger/test/browser_dbg_sources-eval-02.js
new file mode 100644
index 000000000..7d1ba7ebd
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_sources-eval-02.js
@@ -0,0 +1,48 @@
+/* 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;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gEditor = gDebugger.DebuggerView.editor;
+
+ return Task.spawn(function*() {
+ yield waitForSourceShown(gPanel, "-eval.js");
+ is(gSources.values.length, 1, "Should have 1 source");
+
+ let newSource = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.NEW_SOURCE);
+ callInTab(gTab, "evalSourceWithSourceURL");
+ yield newSource;
+
+ is(gSources.values.length, 2, "Should have 2 sources");
+
+ let item = gSources.getItemForAttachment(e => e.label == "bar.js");
+ ok(item, "Source label is incorrect.");
+ is(item.attachment.group, 'http://example.com',
+ 'Source group is incorrect');
+
+ let shown = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
+ gSources.selectedItem = item;
+ yield shown;
+
+ ok(gEditor.getText().indexOf('bar = function() {') === 0,
+ 'Correct source is shown');
+
+ yield closeDebuggerAndFinish(gPanel);
+ });
+ });
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_sources-labels.js b/toolkit/devtools/debugger/test/browser_dbg_sources-labels.js
new file mode 100644
index 000000000..31ff8e174
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_sources-labels.js
@@ -0,0 +1,166 @@
+/* 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";
+
+function test() {
+ let gTab, gPanel, gDebugger;
+ let gSources, gUtils;
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gUtils = gDebugger.SourceUtils;
+
+ let ellipsis = gPanel.panelWin.L10N.ellipsis;
+ 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.");
+
+ // 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/toolkit/devtools/debugger/test/browser_dbg_sources-sorting.js b/toolkit/devtools/debugger/test/browser_dbg_sources-sorting.js
new file mode 100644
index 000000000..89ab6db51
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_sources-sorting.js
@@ -0,0 +1,136 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gSources, gUtils;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+ gUtils = gDebugger.SourceUtils;
+
+ waitForSourceShown(gPanel, ".html")
+ .then(addSourceAndCheckOrder.bind(null, 1))
+ .then(addSourceAndCheckOrder.bind(null, 2))
+ .then(addSourceAndCheckOrder.bind(null, 3))
+ .then(() => { 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/toolkit/devtools/debugger/test/browser_dbg_split-console-paused-reload.js b/toolkit/devtools/debugger/test/browser_dbg_split-console-paused-reload.js
new file mode 100644
index 000000000..70037b94f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_split-console-paused-reload.js
@@ -0,0 +1,42 @@
+/* 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 [,, panel] = yield initDebugger(TAB_URL);
+ 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 result = toolbox.once("webconsole-ready", () => {
+ ok(toolbox.splitConsole, "Split console is shown.");
+ is(dbgWin.gThreadClient.state, "paused", "Execution is still paused.");
+ Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
+ });
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, dbgWin);
+ yield result;
+ yield resumeDebuggerThenCloseAndFinish(panel);
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_stack-01.js b/toolkit/devtools/debugger/test/browser_dbg_stack-01.js
new file mode 100644
index 000000000..013c13d99
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_stack-01.js
@@ -0,0 +1,43 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gFrames, gClassicFrames;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 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/toolkit/devtools/debugger/test/browser_dbg_stack-02.js b/toolkit/devtools/debugger/test/browser_dbg_stack-02.js
new file mode 100644
index 000000000..229f39139
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_stack-02.js
@@ -0,0 +1,109 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gFrames, gClassicFrames;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 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 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,
+ TAB_URL, "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);
+
+ 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);
+
+ 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);
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gFrames = null;
+ gClassicFrames = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_stack-03.js b/toolkit/devtools/debugger/test/browser_dbg_stack-03.js
new file mode 100644
index 000000000..37b0a411a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_stack-03.js
@@ -0,0 +1,73 @@
+/* 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 gTab, gDebuggee, gPanel, gDebugger;
+let gFrames, gClassicFrames, gFramesScrollingInterval;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+ gTab = aTab;
+ gDebuggee = aDebuggee;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
+
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED)
+ .then(performTest);
+
+ gDebuggee.gRecurseLimit = (gDebugger.gCallStackPageSize * 2) + 1;
+ gDebuggee.recurse();
+ });
+}
+
+function performTest() {
+ is(gDebugger.gThreadClient.state, "paused",
+ "Should only be getting stack frames while paused.");
+ is(gFrames.itemCount, gDebugger.gCallStackPageSize,
+ "Should have only the max limit of frames.");
+ is(gClassicFrames.itemCount, gDebugger.gCallStackPageSize,
+ "Should have only the max limit of frames in the mirrored view as well.");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED).then(() => {
+ is(gFrames.itemCount, gDebugger.gCallStackPageSize * 2,
+ "Should now have twice the max limit of frames.");
+ is(gClassicFrames.itemCount, gDebugger.gCallStackPageSize * 2,
+ "Should now have twice the max limit of frames in the mirrored view as well.");
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED).then(() => {
+ is(gFrames.itemCount, gDebuggee.gRecurseLimit,
+ "Should have reached the recurse limit.");
+ is(gClassicFrames.itemCount, gDebuggee.gRecurseLimit,
+ "Should have reached the recurse limit in the mirrored view as well.");
+
+ gDebugger.gThreadClient.resume(() => {
+ window.clearInterval(gFramesScrollingInterval);
+ closeDebuggerAndFinish(gPanel);
+ });
+ });
+ });
+
+ gFramesScrollingInterval = window.setInterval(() => {
+ gFrames.widget._list.scrollByIndex(-1);
+ }, 100);
+}
+
+registerCleanupFunction(function() {
+ window.clearInterval(gFramesScrollingInterval);
+ gFramesScrollingInterval = null;
+
+ gTab = null;
+ gDebuggee = null;
+ gPanel = null;
+ gDebugger = null;
+ gFrames = null;
+ gClassicFrames = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_stack-04.js b/toolkit/devtools/debugger/test/browser_dbg_stack-04.js
new file mode 100644
index 000000000..dfcaab355
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_stack-04.js
@@ -0,0 +1,52 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gFrames, gClassicFrames;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gFrames = gDebugger.DebuggerView.StackFrames;
+ gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 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/toolkit/devtools/debugger/test/browser_dbg_stack-05.js b/toolkit/devtools/debugger/test/browser_dbg_stack-05.js
new file mode 100644
index 000000000..45f61bc18
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_stack-05.js
@@ -0,0 +1,129 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gFrames, gClassicFrames;
+
+function test() {
+ initDebugger(TAB_URL).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(initialChecks)
+ .then(testNewestFrame)
+ .then(testOldestFrame)
+ .then(testAfterResume)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ callInTab(gTab, "firstCall");
+ });
+}
+
+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() {
+ let deferred = promise.defer();
+
+ 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, 1),
+ "Editor caret location is correct (1).");
+
+ // The editor's debug location takes a tick to update.
+ executeSoon(() => {
+ is(gEditor.getDebugLocation(), 5,
+ "Editor debug location is correct.");
+
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function testOldestFrame() {
+ let deferred = promise.defer();
+
+ waitForSourceAndCaret(gPanel, "-01.js", 1).then(waitForTick).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).");
+
+ // The editor's debug location takes a tick to update.
+ executeSoon(() => {
+ is(gEditor.getDebugLocation(), 4,
+ "Editor debug location is correct.");
+
+ deferred.resolve();
+ });
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.querySelector("#stackframe-1"),
+ gDebugger);
+
+ return deferred.promise;
+}
+
+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;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gSources = null;
+ gFrames = null;
+ gClassicFrames = null;
+});
+
diff --git a/toolkit/devtools/debugger/test/browser_dbg_stack-06.js b/toolkit/devtools/debugger/test/browser_dbg_stack-06.js
new file mode 100644
index 000000000..6bf4ca7d0
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_stack-06.js
@@ -0,0 +1,86 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gFrames, gClassicFrames;
+
+function test() {
+ initDebugger(TAB_URL).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", 1).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", 1).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/toolkit/devtools/debugger/test/browser_dbg_stack-07.js b/toolkit/devtools/debugger/test/browser_dbg_stack-07.js
new file mode 100644
index 000000000..d17f958c5
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_stack-07.js
@@ -0,0 +1,107 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gSources, gFrames, gClassicFrames, gToolbar;
+
+function test() {
+ initDebugger(TAB_URL).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 updated = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES);
+ gClassicFrames.selectedIndex = gClassicFrames.itemCount - 1;
+ return updated.then(waitForTick);
+ }
+
+ 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/toolkit/devtools/debugger/test/browser_dbg_step-out.js b/toolkit/devtools/debugger/test/browser_dbg_step-out.js
new file mode 100644
index 000000000..1d376caf0
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_step-out.js
@@ -0,0 +1,79 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gVars;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVars = gDebugger.DebuggerView.Variables;
+
+ testNormalReturn();
+ });
+}
+
+function testNormalReturn() {
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 17).then(() => {
+ waitForCaretAndScopes(gPanel, 19).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.");
+
+ resumeDebuggee().then(() => testReturnWithException());
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("step-out"),
+ gDebugger);
+ });
+
+ sendMouseClickToTab(gTab, content.document.getElementById("return"));
+}
+
+function testReturnWithException() {
+ waitForCaretAndScopes(gPanel, 24).then(() => {
+ waitForCaretAndScopes(gPanel, 27).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.");
+
+ resumeDebuggee().then(() => closeDebuggerAndFinish(gPanel));
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ gDebugger.document.getElementById("step-out"),
+ gDebugger);
+ });
+
+ sendMouseClickToTab(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/toolkit/devtools/debugger/test/browser_dbg_tabactor-01.js b/toolkit/devtools/debugger/test/browser_dbg_tabactor-01.js
new file mode 100644
index 000000000..8e0b92d8f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_tabactor-01.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check extension-added tab actor lifetimes.
+ */
+
+const CHROME_URL = "chrome://mochitests/content/browser/browser/devtools/debugger/test/"
+const ACTORS_URL = CHROME_URL + "testactors.js";
+const TAB_URL = EXAMPLE_URL + "doc_empty-tab-01.html";
+
+let gClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ DebuggerServer.addActors(ACTORS_URL);
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect((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(closeConnection)
+ .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.contains("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);
+}
+
+function closeConnection() {
+ let deferred = promise.defer();
+ gClient.close(deferred.resolve);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gClient = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_tabactor-02.js b/toolkit/devtools/debugger/test/browser_dbg_tabactor-02.js
new file mode 100644
index 000000000..c27b39bbd
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_tabactor-02.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check extension-added tab actor lifetimes.
+ */
+
+const CHROME_URL = "chrome://mochitests/content/browser/browser/devtools/debugger/test/"
+const ACTORS_URL = CHROME_URL + "testactors.js";
+const TAB_URL = EXAMPLE_URL + "doc_empty-tab-01.html";
+
+let gClient;
+
+function test() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ DebuggerServer.addActors(ACTORS_URL);
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect((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(closeConnection)
+ .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.contains("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;
+ });
+}
+
+function closeConnection() {
+ let deferred = promise.defer();
+ gClient.close(deferred.resolve);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gClient = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_terminate-on-tab-close.js b/toolkit/devtools/debugger/test/browser_dbg_terminate-on-tab-close.js
new file mode 100644
index 000000000..6b06f9db6
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_terminate-on-tab-close.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that debuggee scripts are terminated on tab closure.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_terminate-on-tab-close.html";
+
+let gTab, gDebugger, gPanel;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ testTerminate();
+ });
+}
+
+function testTerminate() {
+ gDebugger.gThreadClient.addOneTimeListener("paused", () => {
+ resumeDebuggerThenCloseAndFinish(gPanel).then(function () {
+ ok(true, "should not throw after this point");
+ });
+ });
+
+ callInTab(gTab, "debuggerThenThrow");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_tracing-01.js b/toolkit/devtools/debugger/test/browser_dbg_tracing-01.js
new file mode 100644
index 000000000..116173621
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_tracing-01.js
@@ -0,0 +1,105 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get the expected frame enter/exit logs in the tracer view.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
+
+let gTab, gPanel, gDebugger;
+
+function test() {
+ SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ waitForSourceShown(gPanel, "code_tracing-01.js")
+ .then(() => startTracing(gPanel))
+ .then(clickButton)
+ .then(() => waitForClientEvents(aPanel, "traces"))
+ .then(testTraceLogs)
+ .then(() => stopTracing(gPanel))
+ .then(() => {
+ const deferred = promise.defer();
+ SpecialPowers.popPrefEnv(deferred.resolve);
+ return deferred.promise;
+ })
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+ });
+}
+
+function clickButton() {
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+}
+
+function testTraceLogs() {
+ const onclickLogs = filterTraces(gPanel,
+ t => t.querySelector(".trace-name[value=onclick]"));
+ is(onclickLogs.length, 2, "Should have two logs from 'onclick'");
+ ok(onclickLogs[0].querySelector(".trace-call"),
+ "The first 'onclick' log should be a call.");
+ ok(onclickLogs[1].querySelector(".trace-return"),
+ "The second 'onclick' log should be a return.");
+ for (let t of onclickLogs) {
+ ok(t.querySelector(".trace-item").getAttribute("tooltiptext")
+ .contains("doc_tracing-01.html"));
+ }
+
+ const nonOnclickLogs = filterTraces(gPanel,
+ t => !t.querySelector(".trace-name[value=onclick]"));
+ for (let t of nonOnclickLogs) {
+ ok(t.querySelector(".trace-item").getAttribute("tooltiptext")
+ .contains("code_tracing-01.js"));
+ }
+
+ const mainLogs = filterTraces(gPanel,
+ t => t.querySelector(".trace-name[value=main]"));
+ is(mainLogs.length, 2, "Should have an enter and an exit for 'main'");
+ ok(mainLogs[0].querySelector(".trace-call"),
+ "The first 'main' log should be a call.");
+ ok(mainLogs[1].querySelector(".trace-return"),
+ "The second 'main' log should be a return.");
+
+ const factorialLogs = filterTraces(gPanel,
+ t => t.querySelector(".trace-name[value=factorial]"));
+ is(factorialLogs.length, 10, "Should have 5 enter, and 5 exit frames for 'factorial'");
+ ok(factorialLogs.slice(0, 5).every(t => t.querySelector(".trace-call")),
+ "The first five 'factorial' logs should be calls.");
+ ok(factorialLogs.slice(5).every(t => t.querySelector(".trace-return")),
+ "The second five 'factorial' logs should be returns.")
+
+ // Test that the depth affects padding so that calls are indented properly.
+ let lastDepth = -Infinity;
+ for (let t of factorialLogs.slice(0, 5)) {
+ let depth = parseInt(t.querySelector(".trace-item").style.MozPaddingStart, 10);
+ ok(depth > lastDepth, "The depth should be increasing");
+ lastDepth = depth;
+ }
+ lastDepth = Infinity;
+ for (let t of factorialLogs.slice(5)) {
+ let depth = parseInt(t.querySelector(".trace-item").style.MozPaddingStart, 10);
+ ok(depth < lastDepth, "The depth should be decreasing");
+ lastDepth = depth;
+ }
+
+ const throwerLogs = filterTraces(gPanel,
+ t => t.querySelector(".trace-name[value=thrower]"));
+ is(throwerLogs.length, 2, "Should have an enter and an exit for 'thrower'");
+ ok(throwerLogs[0].querySelector(".trace-call"),
+ "The first 'thrower' log should be a call.");
+ ok(throwerLogs[1].querySelector(".trace-throw",
+ "The second 'thrower' log should be a throw."));
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_tracing-02.js b/toolkit/devtools/debugger/test/browser_dbg_tracing-02.js
new file mode 100644
index 000000000..eb55db161
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_tracing-02.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we highlight matching calls and returns on hover.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
+
+let gTab, gPanel, gDebugger;
+
+function test() {
+ SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ waitForSourceShown(gPanel, "code_tracing-01.js")
+ .then(() => startTracing(gPanel))
+ .then(clickButton)
+ .then(() => waitForClientEvents(aPanel, "traces"))
+ .then(highlightCall)
+ .then(testReturnHighlighted)
+ .then(unhighlightCall)
+ .then(testNoneHighlighted)
+ .then(() => stopTracing(gPanel))
+ .then(() => {
+ const deferred = promise.defer();
+ SpecialPowers.popPrefEnv(deferred.resolve);
+ return deferred.promise;
+ })
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+ });
+}
+
+function clickButton() {
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+}
+
+function highlightCall() {
+ const callTrace = filterTraces(gPanel, t => t.querySelector(".trace-name[value=main]"))[0];
+ EventUtils.sendMouseEvent({ type: "mouseover" },
+ callTrace,
+ gDebugger);
+}
+
+function testReturnHighlighted() {
+ const returnTrace = filterTraces(gPanel, t => t.querySelector(".trace-name[value=main]"))[1];
+ ok(Array.indexOf(returnTrace.querySelector(".trace-item").classList, "selected-matching") >= 0,
+ "The corresponding return log should be highlighted.");
+}
+
+function unhighlightCall() {
+ const callTrace = filterTraces(gPanel, t => t.querySelector(".trace-name[value=main]"))[0];
+ EventUtils.sendMouseEvent({ type: "mouseout" },
+ callTrace,
+ gDebugger);
+}
+
+function testNoneHighlighted() {
+ const highlightedTraces = filterTraces(gPanel, t => t.querySelector(".selected-matching"));
+ is(highlightedTraces.length, 0, "Shouldn't have any highlighted traces");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_tracing-03.js b/toolkit/devtools/debugger/test/browser_dbg_tracing-03.js
new file mode 100644
index 000000000..e8bcbe8f9
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_tracing-03.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+SimpleTest.requestCompleteLog();
+
+/**
+ * Test that we can jump to function definitions by clicking on logs.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
+
+let gTab, gPanel, gDebugger, gSources;
+
+function test() {
+ SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gSources = gDebugger.DebuggerView.Sources;
+
+ waitForSourceShown(gPanel, "code_tracing-01.js")
+ .then(() => startTracing(gPanel))
+ .then(() => clickButton())
+ .then(() => waitForClientEvents(aPanel, "traces"))
+ .then(() => {
+ // Switch away from the JS file so we can make sure that clicking on a
+ // log will switch us back to the correct JS file.
+ gSources.selectedValue = getSourceActor(gSources, TAB_URL);
+ return ensureSourceIs(aPanel, getSourceActor(gSources, TAB_URL), true);
+ })
+ .then(() => {
+ const finished = waitForSourceShown(gPanel, "code_tracing-01.js");
+ clickTraceLog();
+ return finished;
+ })
+ .then(testCorrectLine)
+ .then(() => stopTracing(gPanel))
+ .then(() => {
+ const deferred = promise.defer();
+ SpecialPowers.popPrefEnv(deferred.resolve);
+ return deferred.promise;
+ })
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+ });
+}
+
+function clickButton() {
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+}
+
+function clickTraceLog() {
+ filterTraces(gPanel, t => t.querySelector(".trace-name[value=main]"))[0].click();
+}
+
+function testCorrectLine() {
+ is(gDebugger.DebuggerView.editor.getCursor().line, 18,
+ "The editor should have the function definition site's line selected.");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gSources = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_tracing-04.js b/toolkit/devtools/debugger/test/browser_dbg_tracing-04.js
new file mode 100644
index 000000000..c5976c6cb
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_tracing-04.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that when we click on logs, we get the parameters/return value in the variables view.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
+
+let gTab, gPanel, gDebugger;
+
+function test() {
+ SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ waitForSourceShown(gPanel, "code_tracing-01.js")
+ .then(() => startTracing(gPanel))
+ .then(clickButton)
+ .then(() => waitForClientEvents(aPanel, "traces"))
+ .then(clickTraceCall)
+ .then(testParams)
+ .then(clickTraceReturn)
+ .then(testReturn)
+ .then(() => stopTracing(gPanel))
+ .then(() => {
+ const deferred = promise.defer();
+ SpecialPowers.popPrefEnv(deferred.resolve);
+ return deferred.promise;
+ })
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ DevToolsUtils.reportException("browser_dbg_tracing-04.js", aError);
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+ });
+}
+
+function clickButton() {
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+}
+
+function clickTraceCall() {
+ filterTraces(gPanel, t => t.querySelector(".trace-name[value=factorial]"))[0]
+ .click();
+}
+
+function testParams() {
+ const name = gDebugger.document.querySelector(".variables-view-variable .name");
+ ok(name, "Should have a variable name");
+ is(name.getAttribute("value"), "n", "The variable name should be n");
+
+ const value = gDebugger.document.querySelector(".variables-view-variable .value.token-number");
+ ok(value, "Should have a variable value");
+ is(value.getAttribute("value"), "5", "The variable value should be 5");
+}
+
+function clickTraceReturn() {
+ filterTraces(gPanel, t => t.querySelector(".trace-name[value=factorial]"))
+ .pop().click();
+}
+
+function testReturn() {
+ const name = gDebugger.document.querySelector(".variables-view-variable .name");
+ ok(name, "Should have a variable name");
+ is(name.getAttribute("value"), "<return>", "The variable name should be <return>");
+
+ const value = gDebugger.document.querySelector(".variables-view-variable .value.token-number");
+ ok(value, "Should have a variable value");
+ is(value.getAttribute("value"), "120", "The variable value should be 120");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_tracing-05.js b/toolkit/devtools/debugger/test/browser_dbg_tracing-05.js
new file mode 100644
index 000000000..a51cc0ae1
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_tracing-05.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that text describing the tracing state is correctly displayed.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
+
+let gTab, gPanel, gDebugger;
+let gTracer, gL10N;
+
+function test() {
+ SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, () => {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gTracer = gDebugger.DebuggerView.Tracer;
+ gL10N = gDebugger.L10N;
+
+ waitForSourceShown(gPanel, "code_tracing-01.js")
+ .then(testTracingNotStartedText)
+ .then(() => gTracer._onStartTracing())
+ .then(testFunctionCallsUnavailableText)
+ .then(clickButton)
+ .then(() => waitForClientEvents(aPanel, "traces"))
+ .then(testNoEmptyText)
+ .then(() => gTracer._onClear())
+ .then(testFunctionCallsUnavailableText)
+ .then(() => gTracer._onStopTracing())
+ .then(testTracingNotStartedText)
+ .then(() => gTracer._onClear())
+ .then(testTracingNotStartedText)
+ .then(() => {
+ const deferred = promise.defer();
+ SpecialPowers.popPrefEnv(deferred.resolve);
+ return deferred.promise;
+ })
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ DevToolsUtils.reportException("browser_dbg_tracing-05.js", aError);
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+ });
+}
+
+function testTracingNotStartedText() {
+ let label = gDebugger.document.querySelector("#tracer-tabpanel .fast-list-widget-empty-text");
+ ok(label,
+ "A label is displayed in the tracer tabpanel.");
+ is(label.getAttribute("value"), gL10N.getStr("tracingNotStartedText"),
+ "The correct {{tracingNotStartedText}} is displayed in the tracer tabpanel.");
+}
+
+function testFunctionCallsUnavailableText() {
+ let label = gDebugger.document.querySelector("#tracer-tabpanel .fast-list-widget-empty-text");
+ ok(label,
+ "A label is displayed in the tracer tabpanel.");
+ is(label.getAttribute("value"), gL10N.getStr("noFunctionCallsText"),
+ "The correct {{noFunctionCallsText}} is displayed in the tracer tabpanel.");
+}
+
+function testNoEmptyText() {
+ let label = gDebugger.document.querySelector("#tracer-tabpanel .fast-list-widget-empty-text");
+ ok(!label,
+ "No label should be displayed in the tracer tabpanel.");
+}
+
+function clickButton() {
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gTracer = null;
+ gL10N = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_tracing-06.js b/toolkit/devtools/debugger/test/browser_dbg_tracing-06.js
new file mode 100644
index 000000000..f1f836ef3
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_tracing-06.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that the tracer doesn't connect to the backend when tracing is disabled.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
+const TRACER_PREF = "devtools.debugger.tracer";
+
+let gTab, gPanel, gDebugger;
+let gOriginalPref = Services.prefs.getBoolPref(TRACER_PREF);
+Services.prefs.setBoolPref(TRACER_PREF, false);
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ waitForSourceShown(gPanel, "code_tracing-01.js")
+ .then(() => {
+ ok(!gDebugger.DebuggerController.traceClient, "Should not have a trace client");
+ closeDebuggerAndFinish(gPanel);
+ })
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ Services.prefs.setBoolPref(TRACER_PREF, gOriginalPref);
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_tracing-07.js b/toolkit/devtools/debugger/test/browser_dbg_tracing-07.js
new file mode 100644
index 000000000..4aaba2c41
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_tracing-07.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Execute code both before and after blackboxing and test that we get
+ * appropriately styled traces.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_tracing-01.html";
+
+let gTab, gPanel;
+
+function test() {
+ Task.async(function*() {
+ yield pushPref();
+
+ [gTab,, gPanel] = yield initDebugger(TAB_URL);
+
+ yield startTracing(gPanel);
+ yield clickButton();
+ yield waitForClientEvents(gPanel, "traces");
+
+ /**
+ * Test that there are some traces which are not blackboxed.
+ */
+ const firstBbButton = getBlackBoxButton(gPanel);
+ ok(!firstBbButton.checked, "Should not be black boxed by default");
+
+ const blackBoxedTraces =
+ gPanel.panelWin.document.querySelectorAll(".trace-item.black-boxed");
+ ok(blackBoxedTraces.length === 0, "There should no blackboxed traces.");
+
+ const notBlackBoxedTraces =
+ gPanel.panelWin.document.querySelectorAll(".trace-item:not(.black-boxed)");
+ ok(notBlackBoxedTraces.length > 0,
+ "There should be some traces which are not blackboxed.");
+
+ yield toggleBlackBoxing(gPanel);
+ yield clickButton();
+ yield waitForClientEvents(gPanel, "traces");
+
+ /**
+ * Test that there are some traces which are blackboxed.
+ */
+ const secondBbButton = getBlackBoxButton(gPanel);
+ ok(secondBbButton.checked, "The checkbox should no longer be checked.");
+ const traces =
+ gPanel.panelWin.document.querySelectorAll(".trace-item.black-boxed");
+ ok(traces.length > 0, "There should be some blackboxed traces.");
+
+ yield stopTracing(gPanel);
+ yield popPref();
+ yield closeDebuggerAndFinish(gPanel);
+
+ finish();
+ })().catch(e => {
+ ok(false, "Got an error: " + e.message + "\n" + e.stack);
+ finish();
+ });
+}
+
+function clickButton() {
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+}
+
+function pushPref() {
+ let deferred = promise.defer();
+ SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]},
+ deferred.resolve);
+ return deferred.promise;
+}
+
+function popPref() {
+ let deferred = promise.defer();
+ SpecialPowers.popPrefEnv(deferred.resolve);
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+});
+
diff --git a/toolkit/devtools/debugger/test/browser_dbg_tracing-08.js b/toolkit/devtools/debugger/test/browser_dbg_tracing-08.js
new file mode 100644
index 000000000..eb20ffa9f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_tracing-08.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that tracing about:config doesn't produce errors.
+ */
+
+const TAB_URL = "about:config";
+
+let gPanel, gDoneChecks;
+
+function test() {
+ gDoneChecks = promise.defer();
+ const tracerPref = promise.defer();
+ const configPref = promise.defer();
+ SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, tracerPref.resolve);
+ SpecialPowers.pushPrefEnv({'set': [["general.warnOnAboutConfig", false]]}, configPref.resolve);
+ promise.all([tracerPref.promise, configPref.promise]).then(() => {
+ initDebugger(TAB_URL).then(([,, aPanel]) => {
+ gPanel = aPanel;
+ gPanel.panelWin.gClient.addOneTimeListener("traces", testTraceLogs);
+ }).then(() => startTracing(gPanel))
+ .then(generateTrace)
+ .then(() => waitForClientEvents(gPanel, "traces"))
+ .then(() => gDoneChecks.promise)
+ .then(() => stopTracing(gPanel))
+ .then(resetPreferences)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function testTraceLogs(name, packet) {
+ info("Traces: " + packet.traces.length);
+ ok(packet.traces.length > 0, "Got some traces.");
+ ok(packet.traces.every(t => t.type != "enteredFrame" || !!t.location),
+ "All enteredFrame traces contain location.");
+ gDoneChecks.resolve();
+}
+
+function generateTrace(name, packet) {
+ // Interact with the page to cause JS execution.
+ let search = content.document.getElementById("textbox");
+ info("Interacting with the page.");
+ search.value = "devtools";
+}
+
+function resetPreferences() {
+ const deferred = promise.defer();
+ SpecialPowers.popPrefEnv(() => SpecialPowers.popPrefEnv(deferred.resolve));
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gPanel = null;
+ gDoneChecks = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_variables-view-01.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-01.js
new file mode 100644
index 000000000..a3591eb93
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-01.js
@@ -0,0 +1,126 @@
+/* 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() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ let variables = aPanel.panelWin.DebuggerView.Variables;
+ let testScope = variables.addScope("test");
+
+ ok(testScope,
+ "Should have created a scope.");
+ ok(testScope.id.contains("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.contains("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/toolkit/devtools/debugger/test/browser_dbg_variables-view-02.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-02.js
new file mode 100644
index 000000000..438b07a0f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-02.js
@@ -0,0 +1,221 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that creating, collpasing and expanding variables in the
+ * variables view works as expected.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
+
+function test() {
+ initDebugger(TAB_URL).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, null,
+ "Shouldn't be able to duplicate variables in the same scope.");
+
+ ok(testVar,
+ "Should have created a variable.");
+ ok(testVar.id.contains("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.contains("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.contains("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.contains("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/toolkit/devtools/debugger/test/browser_dbg_variables-view-03.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-03.js
new file mode 100644
index 000000000..4a591ad96
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-03.js
@@ -0,0 +1,151 @@
+/* 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() {
+ initDebugger(TAB_URL).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/toolkit/devtools/debugger/test/browser_dbg_variables-view-04.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-04.js
new file mode 100644
index 000000000..1ad4ff10f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-04.js
@@ -0,0 +1,150 @@
+/* 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() {
+ initDebugger(TAB_URL).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(Cu.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/toolkit/devtools/debugger/test/browser_dbg_variables-view-05.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-05.js
new file mode 100644
index 000000000..71c857fb6
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-05.js
@@ -0,0 +1,228 @@
+/* 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() {
+ initDebugger(TAB_URL).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/toolkit/devtools/debugger/test/browser_dbg_variables-view-06.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-06.js
new file mode 100644
index 000000000..fa6901d08
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-06.js
@@ -0,0 +1,122 @@
+/* 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";
+
+const test = Task.async(function* () {
+ const [tab,, panel] = yield initDebugger(TAB_URL);
+ yield ensureSourceIs(panel, "doc_promise.html", true);
+
+ 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;
+ }
+ }
+
+ debugger;
+
+ resumeDebuggerThenCloseAndFinish(panel);
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_variables-view-accessibility.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-accessibility.js
new file mode 100644
index 000000000..7605b2cca
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-accessibility.js
@@ -0,0 +1,555 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that the variables view is keyboard accessible.
+ */
+
+let gTab, gPanel, gDebugger;
+let gVariablesView;
+
+function test() {
+ initDebugger("about:blank").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/toolkit/devtools/debugger/test/browser_dbg_variables-view-data.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-data.js
new file mode 100644
index 000000000..be2446fb6
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-data.js
@@ -0,0 +1,609 @@
+/* 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.
+ */
+
+let gTab, gPanel, gDebugger;
+let gVariablesView, gScope, gVariable;
+
+function test() {
+ initDebugger("about:blank").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/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-cancel.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-cancel.js
new file mode 100644
index 000000000..7ba20aad1
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-cancel.js
@@ -0,0 +1,52 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-click.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-click.js
new file mode 100644
index 000000000..25aaf46af
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-click.js
@@ -0,0 +1,52 @@
+/* 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 [tab, debuggee, panel] = yield initDebugger(TAB_URL);
+ 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-getset-01.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-getset-01.js
new file mode 100644
index 000000000..d15fd6b2a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-getset-01.js
@@ -0,0 +1,294 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gL10N, gEditor, gVars, gWatch;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ initDebugger(TAB_URL).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() {};
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 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);
+ });
+
+ sendMouseClickToTab(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/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-getset-02.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-getset-02.js
new file mode 100644
index 000000000..7c760b1af
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-getset-02.js
@@ -0,0 +1,101 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gL10N, gEditor, gVars, gWatch;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ initDebugger(TAB_URL).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() {};
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 24)
+ .then(() => addWatchExpression())
+ .then(() => testEdit("\"xlerb\"", "xlerb"))
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ sendMouseClickToTab(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/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-value.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-value.js
new file mode 100644
index 000000000..c58e6c0da
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-value.js
@@ -0,0 +1,85 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gVars;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVars = gDebugger.DebuggerView.Variables;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 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);
+ });
+
+ sendMouseClickToTab(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/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-watch.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-watch.js
new file mode 100644
index 000000000..9f4604af4
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-edit-watch.js
@@ -0,0 +1,502 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gL10N, gEditor, gVars, gWatch;
+
+function test() {
+ initDebugger(TAB_URL).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;
+
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS)
+ .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/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-01.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-01.js
new file mode 100644
index 000000000..4555a0dad
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-01.js
@@ -0,0 +1,218 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gVariables, gSearchBox;
+
+function test() {
+ // Debug test slaves are quite slow at this test.
+ requestLongerTimeout(4);
+
+ initDebugger(TAB_URL).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([
+ waitForSourceAndCaret(gPanel, ".html", 22),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ 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);
+ });
+
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+ });
+}
+
+function testVariablesAndPropertiesFiltering() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalScope = gVariables.getScopeAtIndex(3);
+ 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(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(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(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() {
+ typeText(gSearchBox, "constructor");
+ testFiltered();
+ }
+
+ function secondFilter() {
+ localScope.collapse();
+ withScope.collapse();
+ functionScope.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(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.");
+
+ clearText(gSearchBox);
+ typeText(gSearchBox, "constructor");
+ testFiltered();
+ }
+
+ firstFilter();
+ secondFilter();
+}
+
+function prepareVariablesAndProperties() {
+ let deferred = promise.defer();
+
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalScope = gVariables.getScopeAtIndex(3);
+
+ 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(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(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();
+ globalScope.expand();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+ gSearchBox = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-02.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-02.js
new file mode 100644
index 000000000..9590366f4
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-02.js
@@ -0,0 +1,225 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gVariables, gSearchBox;
+
+function test() {
+ // Debug test slaves are quite slow at this test.
+ requestLongerTimeout(4);
+
+ initDebugger(TAB_URL).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([
+ waitForSourceAndCaret(gPanel, ".html", 22),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ 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);
+ });
+
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+ });
+}
+
+function testVariablesAndPropertiesFiltering() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalScope = gVariables.getScopeAtIndex(3);
+ 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(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(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(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() {
+ typeText(gSearchBox, "\"Function\"");
+ testFiltered();
+ }
+
+ function secondFilter() {
+ localScope.collapse();
+ withScope.collapse();
+ functionScope.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(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);
+ typeText(gSearchBox, "\"Function\"");
+ testFiltered();
+ }
+
+ firstFilter();
+ secondFilter();
+}
+
+function prepareVariablesAndProperties() {
+ let deferred = promise.defer();
+
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalScope = gVariables.getScopeAtIndex(3);
+
+ 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(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(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();
+ globalScope.expand();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+ gSearchBox = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-03.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-03.js
new file mode 100644
index 000000000..6ed1f2135
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-03.js
@@ -0,0 +1,157 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gVariables, gSearchBox;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ initDebugger(TAB_URL).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([
+ waitForSourceAndCaret(gPanel, ".html", 22),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ 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);
+ });
+
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+ });
+}
+
+function testVariablesAndPropertiesFiltering() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalScope = gVariables.getScopeAtIndex(3);
+
+ 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(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(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(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();
+ 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(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 globalScope = gVariables.getScopeAtIndex(3);
+
+ 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(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(globalScope.expanded, true,
+ "The globalScope should now be expanded.");
+
+ deferred.resolve();
+ });
+
+ withScope.expand();
+ functionScope.expand();
+ globalScope.expand();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+ gSearchBox = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-04.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-04.js
new file mode 100644
index 000000000..26b99a93b
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-04.js
@@ -0,0 +1,225 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gEditor, gVariables, gSearchBox;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ initDebugger(TAB_URL).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([
+ waitForSourceAndCaret(gPanel, ".html", 22),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ 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);
+ });
+
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+ });
+}
+
+function testVariablesAndPropertiesFiltering() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalScope = gVariables.getScopeAtIndex(3);
+ let step = 0;
+
+ let tests = [
+ function() {
+ assertExpansion([true, false, false, false]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function() {
+ assertExpansion([true, false, false, false]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function() {
+ assertExpansion([true, false, false, false]);
+ gEditor.focus();
+ },
+ function() {
+ assertExpansion([true, false, false, false]);
+ typeText(gSearchBox, "*");
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ gEditor.focus();
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ backspaceText(gSearchBox, 1);
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ gEditor.focus();
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ localScope.collapse();
+ withScope.collapse();
+ functionScope.collapse();
+ globalScope.collapse();
+ },
+ function() {
+ assertExpansion([false, false, false, false]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function() {
+ assertExpansion([false, false, false, false]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function() {
+ assertExpansion([false, false, false, false]);
+ gEditor.focus();
+ },
+ function() {
+ assertExpansion([false, false, false, false]);
+ clearText(gSearchBox);
+ typeText(gSearchBox, "*");
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ gEditor.focus();
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ backspaceText(gSearchBox, 1);
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ EventUtils.sendKey("RETURN", gDebugger);
+ },
+ function() {
+ assertExpansion([true, true, true, true]);
+ gEditor.focus();
+ },
+ function() {
+ assertExpansion([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(globalScope.expanded, aFlags[3],
+ "The globalScope should " + (aFlags[3] ? "" : "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 globalScope = gVariables.getScopeAtIndex(3);
+
+ 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(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(globalScope.expanded, true,
+ "The globalScope should now be expanded.");
+
+ withScope.collapse();
+ functionScope.collapse();
+ globalScope.collapse();
+
+ deferred.resolve();
+ });
+
+ withScope.expand();
+ functionScope.expand();
+ globalScope.expand();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gEditor = null;
+ gVariables = null;
+ gSearchBox = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-05.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-05.js
new file mode 100644
index 000000000..0e5f0b040
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-05.js
@@ -0,0 +1,233 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gVariables, gSearchBox;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ initDebugger(TAB_URL).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([
+ waitForSourceAndCaret(gPanel, ".html", 22),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ 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);
+ });
+
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+ });
+}
+
+function testVariablesAndPropertiesFiltering() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let withScope = gVariables.getScopeAtIndex(1);
+ let functionScope = gVariables.getScopeAtIndex(2);
+ let globalScope = gVariables.getScopeAtIndex(3);
+ let step = 0;
+
+ let tests = [
+ function() {
+ assertScopeExpansion([true, false, false, false]);
+ typeText(gSearchBox, "*arguments");
+ },
+ function() {
+ assertScopeExpansion([true, true, true, true]);
+ assertVariablesCountAtLeast([0, 0, 1, 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]);
+ assertVariablesCountAtLeast([0, 0, 1, 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]);
+ assertVariablesCountAtLeast([0, 1, 3, 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]);
+ assertVariablesCountAtLeast([4, 1, 3, 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(globalScope.expanded, aFlags[3],
+ "The globalScope should " + (aFlags[3] ? "" : "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(globalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length >= aCounts[3],
+ "There should be " + aCounts[3] +
+ " 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 globalScope = gVariables.getScopeAtIndex(3);
+
+ 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(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(globalScope.expanded, true,
+ "The globalScope should now be expanded.");
+
+ withScope.collapse();
+ functionScope.collapse();
+ globalScope.collapse();
+
+ deferred.resolve();
+ });
+
+ withScope.expand();
+ functionScope.expand();
+ globalScope.expand();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+ gSearchBox = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-pref.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-pref.js
new file mode 100644
index 000000000..364e22d58
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-pref.js
@@ -0,0 +1,79 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gPrefs, gOptions, gVariables;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gPrefs = gDebugger.Prefs;
+ gOptions = gDebugger.DebuggerView.Options;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ waitForSourceShown(gPanel, ".html").then(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/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-searchbox.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-searchbox.js
new file mode 100644
index 000000000..4cb1f7b78
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-filter-searchbox.js
@@ -0,0 +1,144 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gVariables;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ waitForSourceShown(gPanel, ".html").then(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/toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-parameters-01.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-parameters-01.js
new file mode 100644
index 000000000..b29977f89
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-parameters-01.js
@@ -0,0 +1,256 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gVariables;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 24)
+ .then(initialChecks)
+ .then(testExpandVariables)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+ });
+}
+
+function initialChecks() {
+ let scopeNodes = gDebugger.document.querySelectorAll(".variables-view-scope");
+ is(scopeNodes.length, 2,
+ "There should be 2 scopes available.");
+
+ ok(scopeNodes[0].querySelector(".name").getAttribute("value").contains("[test]"),
+ "The local scope should be properly identified.");
+ ok(scopeNodes[1].querySelector(".name").getAttribute("value").contains("[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.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[0]).expanded, true,
+ "The local scope should be expanded by default.");
+ is(gVariables.getItemForNode(scopeNodes[1]).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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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/toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-parameters-02.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-parameters-02.js
new file mode 100644
index 000000000..b1f482317
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-parameters-02.js
@@ -0,0 +1,546 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gVariables;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 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);
+ });
+
+ sendMouseClickToTab(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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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"),
+ "test/myVar.prop()",
+ "Should have the right property value for 'get'.");
+ ok(propNonEnums[0].querySelector(".value").className.contains("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"),
+ "test/myVar.prop(val)",
+ "Should have the right property value for 'set'.");
+ ok(propNonEnums[1].querySelector(".value").className.contains("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/toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-parameters-03.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-parameters-03.js
new file mode 100644
index 000000000..d667ce673
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-parameters-03.js
@@ -0,0 +1,151 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gVariables;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 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);
+ });
+
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+ });
+}
+
+function expandGlobalScope() {
+ let deferred = promise.defer();
+
+ let globalScope = gVariables.getScopeAtIndex(1);
+ 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(1);
+ 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(1).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(1).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/toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-with.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-with.js
new file mode 100644
index 000000000..b96032174
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-frame-with.js
@@ -0,0 +1,207 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gVariables;
+
+function test() {
+ initDebugger(TAB_URL).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([
+ waitForSourceAndCaret(gPanel, ".html", 22),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ 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);
+ });
+
+ sendMouseClickToTab(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").contains("[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.contains("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.contains("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.contains("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.contains("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").contains("[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.contains("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.contains("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.contains("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.contains("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").contains("[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.contains("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.contains("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.contains("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.contains("token-number"),
+ "Should have the right token class for 'foo'.");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_variables-view-frozen-sealed-nonext.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-frozen-sealed-nonext.js
new file mode 100644
index 000000000..33eafc162
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-frozen-sealed-nonext.js
@@ -0,0 +1,87 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+
+function test() {
+ initDebugger(TAB_URL).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/toolkit/devtools/debugger/test/browser_dbg_variables-view-hide-non-enums.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-hide-non-enums.js
new file mode 100644
index 000000000..2030a82f9
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-hide-non-enums.js
@@ -0,0 +1,105 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js
new file mode 100644
index 000000000..d543b49a4
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-large-array-buffer.js
@@ -0,0 +1,239 @@
+/* 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.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_large-array-buffer.html";
+
+let gTab, gPanel, gDebugger;
+let gVariables, gEllipsis;
+
+function test() {
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+ gEllipsis = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 23)
+ .then(() => initialChecks())
+ .then(() => verifyFirstLevel())
+ .then(() => verifyNextLevels())
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+ });
+}
+
+function initialChecks() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let bufferVar = localScope.get("buffer");
+ let arrayVar = localScope.get("largeArray");
+ let objectVar = localScope.get("largeObject");
+
+ ok(bufferVar, "There should be a 'buffer' variable present in the scope.");
+ ok(arrayVar, "There should be a 'largeArray' variable present in the scope.");
+ ok(objectVar, "There should be a 'largeObject' variable present in the scope.");
+
+ is(bufferVar.target.querySelector(".name").getAttribute("value"), "buffer",
+ "Should have the right property name for 'buffer'.");
+ is(bufferVar.target.querySelector(".value").getAttribute("value"), "ArrayBuffer",
+ "Should have the right property value for 'buffer'.");
+ ok(bufferVar.target.querySelector(".value").className.contains("token-other"),
+ "Should have the right token class for 'buffer'.");
+
+ is(arrayVar.target.querySelector(".name").getAttribute("value"), "largeArray",
+ "Should have the right property name for 'largeArray'.");
+ is(arrayVar.target.querySelector(".value").getAttribute("value"), "Int8Array[10000]",
+ "Should have the right property value for 'largeArray'.");
+ ok(arrayVar.target.querySelector(".value").className.contains("token-other"),
+ "Should have the right token class for 'largeArray'.");
+
+ is(objectVar.target.querySelector(".name").getAttribute("value"), "largeObject",
+ "Should have the right property name for 'largeObject'.");
+ is(objectVar.target.querySelector(".value").getAttribute("value"), "Object",
+ "Should have the right property value for 'largeObject'.");
+ ok(objectVar.target.querySelector(".value").className.contains("token-other"),
+ "Should have the right token class for 'largeObject'.");
+
+ is(bufferVar.expanded, false,
+ "The 'buffer' variable shouldn't be expanded.");
+ is(arrayVar.expanded, false,
+ "The 'largeArray' variable shouldn't be expanded.");
+ is(objectVar.expanded, false,
+ "The 'largeObject' variable shouldn't be expanded.");
+
+ let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 2);
+ arrayVar.expand();
+ objectVar.expand();
+ return finished;
+}
+
+function verifyFirstLevel() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let arrayVar = localScope.get("largeArray");
+ let objectVar = localScope.get("largeObject");
+
+ let arrayEnums = arrayVar.target.querySelector(".variables-view-element-details.enum").childNodes;
+ let arrayNonEnums = arrayVar.target.querySelector(".variables-view-element-details.nonenum").childNodes;
+ is(arrayEnums.length, 0,
+ "The 'largeArray' shouldn't contain any enumerable elements.");
+ is(arrayNonEnums.length, 9,
+ "The 'largeArray' should contain all the created non-enumerable elements.");
+
+ let objectEnums = objectVar.target.querySelector(".variables-view-element-details.enum").childNodes;
+ let objectNonEnums = objectVar.target.querySelector(".variables-view-element-details.nonenum").childNodes;
+ is(objectEnums.length, 0,
+ "The 'largeObject' shouldn't contain any enumerable elements.");
+ is(objectNonEnums.length, 5,
+ "The 'largeObject' should contain all the created non-enumerable elements.");
+
+ is(arrayVar.target.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"),
+ 0 + gEllipsis + 1999, "The first page in the 'largeArray' is named correctly.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"),
+ "", "The first page in the 'largeArray' should not have a corresponding value.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"),
+ 2000 + gEllipsis + 3999, "The second page in the 'largeArray' is named correctly.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"),
+ "", "The second page in the 'largeArray' should not have a corresponding value.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"),
+ 4000 + gEllipsis + 5999, "The third page in the 'largeArray' is named correctly.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .value")[2].getAttribute("value"),
+ "", "The third page in the 'largeArray' should not have a corresponding value.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"),
+ 6000 + gEllipsis + 9999, "The fourth page in the 'largeArray' is named correctly.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .value")[3].getAttribute("value"),
+ "", "The fourth page in the 'largeArray' should not have a corresponding value.");
+
+ is(objectVar.target.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"),
+ 0 + gEllipsis + 1999, "The first page in the 'largeObject' is named correctly.");
+ is(objectVar.target.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"),
+ "", "The first page in the 'largeObject' should not have a corresponding value.");
+ is(objectVar.target.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"),
+ 2000 + gEllipsis + 3999, "The second page in the 'largeObject' is named correctly.");
+ is(objectVar.target.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"),
+ "", "The second page in the 'largeObject' should not have a corresponding value.");
+ is(objectVar.target.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"),
+ 4000 + gEllipsis + 5999, "The thrid page in the 'largeObject' is named correctly.");
+ is(objectVar.target.querySelectorAll(".variables-view-property .value")[2].getAttribute("value"),
+ "", "The thrid page in the 'largeObject' should not have a corresponding value.");
+ is(objectVar.target.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"),
+ 6000 + gEllipsis + 9999, "The fourth page in the 'largeObject' is named correctly.");
+ is(objectVar.target.querySelectorAll(".variables-view-property .value")[3].getAttribute("value"),
+ "", "The fourth page in the 'largeObject' should not have a corresponding value.");
+
+ is(arrayVar.target.querySelectorAll(".variables-view-property .name")[4].getAttribute("value"),
+ "length", "The other properties 'largeArray' are named correctly.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .value")[4].getAttribute("value"),
+ "10000", "The other properties 'largeArray' have the correct value.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .name")[5].getAttribute("value"),
+ "buffer", "The other properties 'largeArray' are named correctly.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .value")[5].getAttribute("value"),
+ "ArrayBuffer", "The other properties 'largeArray' have the correct value.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .name")[6].getAttribute("value"),
+ "byteLength", "The other properties 'largeArray' are named correctly.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .value")[6].getAttribute("value"),
+ "10000", "The other properties 'largeArray' have the correct value.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .name")[7].getAttribute("value"),
+ "byteOffset", "The other properties 'largeArray' are named correctly.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .value")[7].getAttribute("value"),
+ "0", "The other properties 'largeArray' have the correct value.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .name")[8].getAttribute("value"),
+ "__proto__", "The other properties 'largeArray' are named correctly.");
+ is(arrayVar.target.querySelectorAll(".variables-view-property .value")[8].getAttribute("value"),
+ "Int8ArrayPrototype", "The other properties 'largeArray' have the correct value.");
+
+ is(objectVar.target.querySelectorAll(".variables-view-property .name")[4].getAttribute("value"),
+ "__proto__", "The other properties 'largeObject' are named correctly.");
+ is(objectVar.target.querySelectorAll(".variables-view-property .value")[4].getAttribute("value"),
+ "Object", "The other properties 'largeObject' have the correct value.");
+}
+
+function verifyNextLevels() {
+ let localScope = gVariables.getScopeAtIndex(0);
+ let objectVar = localScope.get("largeObject");
+
+ let lastPage1 = objectVar.get(6000 + gEllipsis + 9999);
+ ok(lastPage1, "The last page in the first level was retrieved successfully.");
+ lastPage1.expand();
+
+ let pageEnums1 = lastPage1.target.querySelector(".variables-view-element-details.enum").childNodes;
+ let pageNonEnums1 = lastPage1.target.querySelector(".variables-view-element-details.nonenum").childNodes;
+ is(pageEnums1.length, 0,
+ "The last page in the first level shouldn't contain any enumerable elements.");
+ is(pageNonEnums1.length, 4,
+ "The last page in the first level should contain all the created non-enumerable elements.");
+
+ is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"),
+ 6000 + gEllipsis + 6999, "The first page in this level named correctly (1).");
+ is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"),
+ 7000 + gEllipsis + 7999, "The second page in this level named correctly (1).");
+ is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"),
+ 8000 + gEllipsis + 8999, "The third page in this level named correctly (1).");
+ is(lastPage1._nonenum.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"),
+ 9000 + gEllipsis + 9999, "The fourth page in this level named correctly (1).");
+
+ let lastPage2 = lastPage1.get(9000 + gEllipsis + 9999);
+ ok(lastPage2, "The last page in the second level was retrieved successfully.");
+ lastPage2.expand();
+
+ let pageEnums2 = lastPage2.target.querySelector(".variables-view-element-details.enum").childNodes;
+ let pageNonEnums2 = lastPage2.target.querySelector(".variables-view-element-details.nonenum").childNodes;
+ is(pageEnums2.length, 0,
+ "The last page in the second level shouldn't contain any enumerable elements.");
+ is(pageNonEnums2.length, 4,
+ "The last page in the second level should contain all the created non-enumerable elements.");
+
+ is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"),
+ 9000 + gEllipsis + 9199, "The first page in this level named correctly (2).");
+ is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"),
+ 9200 + gEllipsis + 9399, "The second page in this level named correctly (2).");
+ is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"),
+ 9400 + gEllipsis + 9599, "The third page in this level named correctly (2).");
+ is(lastPage2._nonenum.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"),
+ 9600 + gEllipsis + 9999, "The fourth page in this level named correctly (2).");
+
+ let lastPage3 = lastPage2.get(9600 + gEllipsis + 9999);
+ ok(lastPage3, "The last page in the third level was retrieved successfully.");
+ lastPage3.expand();
+
+ let pageEnums3 = lastPage3.target.querySelector(".variables-view-element-details.enum").childNodes;
+ let pageNonEnums3 = lastPage3.target.querySelector(".variables-view-element-details.nonenum").childNodes;
+ is(pageEnums3.length, 400,
+ "The last page in the third level should contain all the created enumerable elements.");
+ is(pageNonEnums3.length, 0,
+ "The last page in the third level shouldn't contain any non-enumerable elements.");
+
+ is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"),
+ 9600, "The properties in this level are named correctly (3).");
+ is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"),
+ 9601, "The properties in this level are named correctly (3).");
+ is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[398].getAttribute("value"),
+ 9998, "The properties in this level are named correctly (3).");
+ is(lastPage3._enum.querySelectorAll(".variables-view-property .name")[399].getAttribute("value"),
+ 9999, "The properties in this level are named correctly (3).");
+
+ is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"),
+ 399, "The properties in this level have the correct value (3).");
+ is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"),
+ 398, "The properties in this level have the correct value (3).");
+ is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[398].getAttribute("value"),
+ 1, "The properties in this level have the correct value (3).");
+ is(lastPage3._enum.querySelectorAll(".variables-view-property .value")[399].getAttribute("value"),
+ 0, "The properties in this level have the correct value (3).");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gVariables = null;
+ gEllipsis = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_variables-view-override-01.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-override-01.js
new file mode 100644
index 000000000..7b2911c38
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-override-01.js
@@ -0,0 +1,227 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ let win = panel.panelWin;
+ let events = win.EVENTS;
+ let variables = win.DebuggerView.Variables;
+
+ callInTab(tab, "test");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 23);
+
+ let firstScope = variables.getScopeAtIndex(0);
+ let secondScope = variables.getScopeAtIndex(1);
+ let thirdScope = variables.getScopeAtIndex(2);
+ let globalScope = variables.getScopeAtIndex(3);
+
+ ok(firstScope, "The first scope is available.");
+ ok(secondScope, "The second scope is available.");
+ ok(thirdScope, "The third 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(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(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(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();
+ 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-override-02.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-override-02.js
new file mode 100644
index 000000000..641293d11
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-override-02.js
@@ -0,0 +1,67 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ 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;
+
+ callInTab(tab, "test");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 23);
+ 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-01.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-01.js
new file mode 100644
index 000000000..5349643fd
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-01.js
@@ -0,0 +1,59 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ 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.contains(className),
+ "The inspected property's value is colorized correctly.");
+ }
+
+ callInTab(tab, "start");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
+
+ // 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-02.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-02.js
new file mode 100644
index 000000000..9f8a3d750
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-02.js
@@ -0,0 +1,45 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ 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.contains(className),
+ "The inspected property's value is colorized correctly.");
+ }
+
+ callInTab(tab, "start");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
+
+ // 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-03.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-03.js
new file mode 100644
index 000000000..4ed3375be
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-03.js
@@ -0,0 +1,42 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+
+ callInTab(tab, "start");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
+
+ // 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-04.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-04.js
new file mode 100644
index 000000000..3d4a43bd1
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-04.js
@@ -0,0 +1,31 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+
+ callInTab(tab, "start");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
+
+ // 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-05.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-05.js
new file mode 100644
index 000000000..846f063ec
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-05.js
@@ -0,0 +1,50 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ 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.");
+ }
+
+ callInTab(tab, "start");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
+
+ // Inspect variable.
+ yield openVarPopup(panel, { line: 16, ch: 12 }, true);
+ verifyContents();
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-06.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-06.js
new file mode 100644
index 000000000..1855e1ba1
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-06.js
@@ -0,0 +1,76 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ 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.");
+ }
+
+ callInTab(tab, "start");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
+
+ // Inspect variable.
+ yield openVarPopup(panel, { line: 17, ch: 12 }, true);
+ verifyContents();
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-07.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-07.js
new file mode 100644
index 000000000..5fd51669b
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-07.js
@@ -0,0 +1,63 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ 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.contains(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.");
+ }
+
+ callInTab(tab, "start");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
+
+ // 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-08.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-08.js
new file mode 100644
index 000000000..580b10090
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-08.js
@@ -0,0 +1,67 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ 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.contains(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.");
+ }
+
+ callInTab(tab, "test");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 20);
+ 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-09.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-09.js
new file mode 100644
index 000000000..a06587775
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-09.js
@@ -0,0 +1,32 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ callInTab(tab, "test");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 15);
+
+ 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-10.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-10.js
new file mode 100644
index 000000000..0905a0551
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-10.js
@@ -0,0 +1,60 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ 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;
+
+ callInTab(tab, "start");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
+
+ 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-11.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-11.js
new file mode 100644
index 000000000..5fc86988e
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-11.js
@@ -0,0 +1,77 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ 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.");
+ }
+
+ callInTab(tab, "start");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 19);
+
+ // 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-12.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-12.js
new file mode 100644
index 000000000..f4b290ae8
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-12.js
@@ -0,0 +1,70 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ 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.");
+ }
+
+ callInTab(tab, "start");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 19);
+
+ // 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-13.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-13.js
new file mode 100644
index 000000000..fad68f92a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-13.js
@@ -0,0 +1,61 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ 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");
+ }
+
+ callInTab(tab, "start");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 19);
+
+ // 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-14.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-14.js
new file mode 100644
index 000000000..c70c6fd11
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-14.js
@@ -0,0 +1,48 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+
+ callInTab(tab, "start");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
+
+ // 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-15.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-15.js
new file mode 100644
index 000000000..1a9e40947
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-15.js
@@ -0,0 +1,32 @@
+/* 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ let win = panel.panelWin;
+ let bubble = win.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+
+ callInTab(tab, "start");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 24);
+
+ 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-16.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-16.js
new file mode 100644
index 000000000..2f822a14e
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-popup-16.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * 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 [tab,, panel] = yield initDebugger(TAB_URL);
+ 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(1);
+ is(globalScope.expanded, false,
+ "The globalScope should not be expanded yet.");
+
+ let finished = waitForDebuggerEvents(panel, events.FETCHED_VARIABLES);
+ globalScope.expand();
+ return finished;
+ }
+
+ callInTab(tab, "recurse");
+ yield waitForSourceAndCaretAndScopes(panel, ".html", 26);
+ 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/toolkit/devtools/debugger/test/browser_dbg_variables-view-reexpand-01.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-reexpand-01.js
new file mode 100644
index 000000000..ce9c74eef
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-reexpand-01.js
@@ -0,0 +1,201 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gBreakpoints, gSources, gVariables;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(4);
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gSources = gDebugger.DebuggerView.Sources;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ // Always expand all items between pauses except 'window' variables.
+ gVariables.commitHierarchyIgnoredItems = Object.create(null, { window: { value: true } });
+
+ waitForSourceShown(gPanel, ".html")
+ .then(addBreakpoint)
+ .then(() => ensureThreadClientState(gPanel, "resumed"))
+ .then(pauseDebuggee)
+ .then(prepareVariablesAndProperties)
+ .then(stepInDebuggee)
+ .then(testVariablesExpand)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function addBreakpoint() {
+ return gBreakpoints.addBreakpoint({ actor: gSources.selectedValue, line: 21 });
+}
+
+function pauseDebuggee() {
+ sendMouseClickToTab(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 globalScope = gVariables.getScopeAtIndex(3);
+
+ 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(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(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(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 globalScope = gVariables.getScopeAtIndex(3);
+
+ 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(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(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();
+ globalScope.expand();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gBreakpoints = null;
+ gSources = null;
+ gVariables = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_variables-view-reexpand-02.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-reexpand-02.js
new file mode 100644
index 000000000..1afa7370f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-reexpand-02.js
@@ -0,0 +1,216 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gBreakpoints, gSources, gVariables;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(4);
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gSources = gDebugger.DebuggerView.Sources;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ // Always expand all items between pauses.
+ gVariables.commitHierarchyIgnoredItems = Object.create(null);
+
+ waitForSourceShown(gPanel, ".html")
+ .then(addBreakpoint)
+ .then(() => ensureThreadClientState(gPanel, "resumed"))
+ .then(pauseDebuggee)
+ .then(prepareVariablesAndProperties)
+ .then(stepInDebuggee)
+ .then(testVariablesExpand)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function addBreakpoint() {
+ return gBreakpoints.addBreakpoint({ actor: gSources.selectedValue, line: 21 });
+}
+
+function pauseDebuggee() {
+ sendMouseClickToTab(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 globalScope = gVariables.getScopeAtIndex(3);
+
+ 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(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(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(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 globalScope = gVariables.getScopeAtIndex(3);
+
+ 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(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(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();
+ globalScope.expand();
+
+ return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gBreakpoints = null;
+ gSources = null;
+ gVariables = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_variables-view-reexpand-03.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-reexpand-03.js
new file mode 100644
index 000000000..2b94674eb
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-reexpand-03.js
@@ -0,0 +1,123 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gBreakpoints, gSources, gVariables;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(4);
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gBreakpoints = gDebugger.DebuggerController.Breakpoints;
+ gSources = gDebugger.DebuggerView.Sources;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ // Always expand all items between pauses.
+ gVariables.commitHierarchyIgnoredItems = Object.create(null);
+
+ waitForSourceShown(gPanel, ".html")
+ .then(addBreakpoint)
+ .then(() => ensureThreadClientState(gPanel, "resumed"))
+ .then(pauseDebuggee)
+ .then(prepareScopes)
+ .then(resumeDebuggee)
+ .then(testVariablesExpand)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+}
+
+function addBreakpoint() {
+ return gBreakpoints.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.");
+}
+
+registerCleanupFunction(function() {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ gBreakpoints = null;
+ gSources = null;
+ gVariables = null;
+});
diff --git a/toolkit/devtools/debugger/test/browser_dbg_variables-view-webidl.js b/toolkit/devtools/debugger/test/browser_dbg_variables-view-webidl.js
new file mode 100644
index 000000000..153fe7499
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_variables-view-webidl.js
@@ -0,0 +1,256 @@
+/* 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";
+
+let gTab, gPanel, gDebugger;
+let gVariables;
+
+function test() {
+ // Debug test slaves are a bit slow at this test.
+ requestLongerTimeout(2);
+
+ initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ gVariables = gDebugger.DebuggerView.Variables;
+
+ waitForSourceAndCaretAndScopes(gPanel, ".html", 24)
+ .then(expandGlobalScope)
+ .then(performTest)
+ .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+
+ sendMouseClickToTab(gTab, content.document.querySelector("button"));
+ });
+}
+
+function expandGlobalScope() {
+ let deferred = promise.defer();
+
+ let globalScope = gVariables.getScopeAtIndex(1);
+ 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(1);
+
+ 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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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.contains("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/toolkit/devtools/debugger/test/browser_dbg_watch-expressions-01.js b/toolkit/devtools/debugger/test/browser_dbg_watch-expressions-01.js
new file mode 100644
index 000000000..51a6d775a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_watch-expressions-01.js
@@ -0,0 +1,227 @@
+/* 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;
+
+ initDebugger(TAB_URL).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 });
+
+ waitForSourceShown(gPanel, ".html")
+ .then(() => performTest())
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ 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);
+
+ for (let i = 0; i < aString.length; i++) {
+ EventUtils.sendChar(aString[i], 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/toolkit/devtools/debugger/test/browser_dbg_watch-expressions-02.js b/toolkit/devtools/debugger/test/browser_dbg_watch-expressions-02.js
new file mode 100644
index 000000000..4d4b785b9
--- /dev/null
+++ b/toolkit/devtools/debugger/test/browser_dbg_watch-expressions-02.js
@@ -0,0 +1,371 @@
+/* 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;
+
+ initDebugger(TAB_URL).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 });
+
+ waitForSourceShown(gPanel, ".html", 1)
+ .then(addExpressions)
+ .then(performTest)
+ .then(finishTest)
+ .then(() => closeDebuggerAndFinish(gPanel))
+ .then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+ });
+
+ function addExpressions() {
+ 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, 27,
+ "There should be 27 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, 27,
+ "There should be 27 visible nodes in the watch expressions container");
+ }
+
+ function test1(aCallback) {
+ gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => {
+ checkWatchExpressions(26, {
+ 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(26, {
+ 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(26, {
+ 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(27, {
+ 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(27, {
+ 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(27, {
+ 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(27, {
+ 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(27, {
+ 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\" } }");
+
+ 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.");
+
+ 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.");
+ }
+}
diff --git a/toolkit/devtools/debugger/test/code_binary_search.coffee b/toolkit/devtools/debugger/test/code_binary_search.coffee
new file mode 100644
index 000000000..e3dacdaaa
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_binary_search.js b/toolkit/devtools/debugger/test/code_binary_search.js
new file mode 100644
index 000000000..c43848a60
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_binary_search.map b/toolkit/devtools/debugger/test/code_binary_search.map
new file mode 100644
index 000000000..8d2251125
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_blackboxing_blackboxme.js b/toolkit/devtools/debugger/test/code_blackboxing_blackboxme.js
new file mode 100644
index 000000000..713b3d50d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/code_blackboxing_blackboxme.js
@@ -0,0 +1,9 @@
+function blackboxme(fn) {
+ (function one() {
+ (function two() {
+ (function three() {
+ fn();
+ }());
+ }());
+ }());
+}
diff --git a/toolkit/devtools/debugger/test/code_blackboxing_one.js b/toolkit/devtools/debugger/test/code_blackboxing_one.js
new file mode 100644
index 000000000..7f37b02ad
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_blackboxing_three.js b/toolkit/devtools/debugger/test/code_blackboxing_three.js
new file mode 100644
index 000000000..55ed6c4da
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_blackboxing_two.js b/toolkit/devtools/debugger/test/code_blackboxing_two.js
new file mode 100644
index 000000000..4790ea4a7
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_breakpoints-break-on-last-line-of-script-on-reload.js b/toolkit/devtools/debugger/test/code_breakpoints-break-on-last-line-of-script-on-reload.js
new file mode 100644
index 000000000..a8e8a7973
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_breakpoints-other-tabs.js b/toolkit/devtools/debugger/test/code_breakpoints-other-tabs.js
new file mode 100644
index 000000000..2cf53ba2d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/code_breakpoints-other-tabs.js
@@ -0,0 +1,4 @@
+function testCase() {
+ var foo = "break on me";
+ debugger;
+}
diff --git a/toolkit/devtools/debugger/test/code_frame-script.js b/toolkit/devtools/debugger/test/code_frame-script.js
new file mode 100644
index 000000000..c42803b10
--- /dev/null
+++ b/toolkit/devtools/debugger/test/code_frame-script.js
@@ -0,0 +1,33 @@
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const { loadSubScript } = Cc['@mozilla.org/moz/jssubscript-loader;1'].
+ getService(Ci.mozIJSSubScriptLoader);
+
+const EventUtils = {};
+loadSubScript("chrome://marionette/content/EventUtils.js", EventUtils);
+
+dump("Frame script loaded.\n");
+
+addMessageListener("test:call", function (message) {
+ dump("Calling function with name " + message.data.name + ".\n");
+
+ let data = message.data;
+ XPCNativeWrapper.unwrap(content)[data.name].apply(undefined, data.args);
+ sendAsyncMessage("test:call");
+});
+
+addMessageListener("test:click", function (message) {
+ dump("Sending mouse click.\n");
+
+ let target = message.objects.target;
+ EventUtils.synthesizeMouseAtCenter(target, {},
+ target.ownerDocument.defaultView);
+});
+
+addMessageListener("test:eval", function (message) {
+ dump("Evalling string " + message.data.string + ".\n");
+
+ content.eval(message.data.string);
+ sendAsyncMessage("test:eval");
+});
diff --git a/toolkit/devtools/debugger/test/code_function-search-01.js b/toolkit/devtools/debugger/test/code_function-search-01.js
new file mode 100644
index 000000000..b5d647cfe
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_function-search-02.js b/toolkit/devtools/debugger/test/code_function-search-02.js
new file mode 100644
index 000000000..10b48518f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_function-search-03.js b/toolkit/devtools/debugger/test/code_function-search-03.js
new file mode 100644
index 000000000..e64292a92
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_location-changes.js b/toolkit/devtools/debugger/test/code_location-changes.js
new file mode 100644
index 000000000..d164b8bdf
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_math.js b/toolkit/devtools/debugger/test/code_math.js
new file mode 100644
index 000000000..f765817bb
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_math.map b/toolkit/devtools/debugger/test/code_math.map
new file mode 100644
index 000000000..474304c39
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_math.min.js b/toolkit/devtools/debugger/test/code_math.min.js
new file mode 100644
index 000000000..7d1fb48f0
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_math_bogus_map.js b/toolkit/devtools/debugger/test/code_math_bogus_map.js
new file mode 100644
index 000000000..82e156b10
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_same-line-functions.js b/toolkit/devtools/debugger/test/code_same-line-functions.js
new file mode 100644
index 000000000..58643f59d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/code_same-line-functions.js
@@ -0,0 +1 @@
+function first() { var a = "first"; second(); function second() { var a = "second"; } } \ No newline at end of file
diff --git a/toolkit/devtools/debugger/test/code_script-eval.js b/toolkit/devtools/debugger/test/code_script-eval.js
new file mode 100644
index 000000000..c7485ac7b
--- /dev/null
+++ b/toolkit/devtools/debugger/test/code_script-eval.js
@@ -0,0 +1,10 @@
+
+var bar;
+
+function evalSource() {
+ eval('bar = function() {\nvar x = 5;\n}');
+}
+
+function evalSourceWithSourceURL() {
+ eval('bar = function() {\nvar x = 6;\n} //# sourceURL=bar.js');
+}
diff --git a/toolkit/devtools/debugger/test/code_script-switching-01.js b/toolkit/devtools/debugger/test/code_script-switching-01.js
new file mode 100644
index 000000000..4ba2772de
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_script-switching-02.js b/toolkit/devtools/debugger/test/code_script-switching-02.js
new file mode 100644
index 000000000..feb74315f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_test-editor-mode b/toolkit/devtools/debugger/test/code_test-editor-mode
new file mode 100644
index 000000000..ca8a90889
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_tracing-01.js b/toolkit/devtools/debugger/test/code_tracing-01.js
new file mode 100644
index 000000000..81fc9a7c6
--- /dev/null
+++ b/toolkit/devtools/debugger/test/code_tracing-01.js
@@ -0,0 +1,29 @@
+function factorial(n) {
+ if (n <= 1) {
+ return 1;
+ } else {
+ return n * factorial(n - 1);
+ }
+}
+
+function* yielder(n) {
+ while (n-- >= 0) {
+ yield { value: n, squared: n * n };
+ }
+}
+
+function thrower() {
+ throw new Error("Curse your sudden but inevitable betrayal!");
+}
+
+function main() {
+ factorial(5);
+
+ // XXX bug 923729: Can't test yielding yet.
+ // for (let x of yielder(5)) {}
+
+ try {
+ thrower();
+ } catch (e) {
+ }
+}
diff --git a/toolkit/devtools/debugger/test/code_ugly-2.js b/toolkit/devtools/debugger/test/code_ugly-2.js
new file mode 100644
index 000000000..15fba0701
--- /dev/null
+++ b/toolkit/devtools/debugger/test/code_ugly-2.js
@@ -0,0 +1 @@
+function main2() { var a = 1 + 3; var b = a++; return b + a; }
diff --git a/toolkit/devtools/debugger/test/code_ugly-3.js b/toolkit/devtools/debugger/test/code_ugly-3.js
new file mode 100644
index 000000000..0424b288c
--- /dev/null
+++ b/toolkit/devtools/debugger/test/code_ugly-3.js
@@ -0,0 +1 @@
+function main3() { var a = 1; debugger; noop(a); return 10; };
diff --git a/toolkit/devtools/debugger/test/code_ugly-4.js b/toolkit/devtools/debugger/test/code_ugly-4.js
new file mode 100644
index 000000000..90c2eca64
--- /dev/null
+++ b/toolkit/devtools/debugger/test/code_ugly-4.js
@@ -0,0 +1,24 @@
+function a(){b()}function b(){debugger}
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWJjLmpzIiwic291cmNlcyI6WyJkYXRhOnRleHQvamF2YXNjcmlwdCxmdW5jdGlvbiBhKCl7YigpfSIsImRhdGE6dGV4dC9qYXZhc2NyaXB0LGZ1bmN0aW9uIGIoKXtkZWJ1Z2dlcn0iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsaUJDQUEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMifQ==
+
+// Generate this file by evaluating the following in a browser-environment
+// scratchpad:
+//
+// Components.utils.import('resource://gre/modules/devtools/SourceMap.jsm');
+//
+// 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/toolkit/devtools/debugger/test/code_ugly-5.js b/toolkit/devtools/debugger/test/code_ugly-5.js
new file mode 100644
index 000000000..248eb3bd5
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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){debugger;}
+foo(); \ No newline at end of file
diff --git a/toolkit/devtools/debugger/test/code_ugly-6.js b/toolkit/devtools/debugger/test/code_ugly-6.js
new file mode 100644
index 000000000..0c678c140
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_ugly-7.js b/toolkit/devtools/debugger/test/code_ugly-7.js
new file mode 100644
index 000000000..8ce53b305
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_ugly-8 b/toolkit/devtools/debugger/test/code_ugly-8
new file mode 100644
index 000000000..dc0d18500
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/code_ugly-8^headers^ b/toolkit/devtools/debugger/test/code_ugly-8^headers^
new file mode 100644
index 000000000..a17a9a3a1
--- /dev/null
+++ b/toolkit/devtools/debugger/test/code_ugly-8^headers^
@@ -0,0 +1 @@
+Content-Type: application/javascript
diff --git a/toolkit/devtools/debugger/test/code_ugly.js b/toolkit/devtools/debugger/test/code_ugly.js
new file mode 100644
index 000000000..dc0d18500
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_auto-pretty-print-01.html b/toolkit/devtools/debugger/test/doc_auto-pretty-print-01.html
new file mode 100644
index 000000000..dee2d52f2
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_auto-pretty-print-02.html b/toolkit/devtools/debugger/test/doc_auto-pretty-print-02.html
new file mode 100644
index 000000000..e96a63d9e
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_binary_search.html b/toolkit/devtools/debugger/test/doc_binary_search.html
new file mode 100644
index 000000000..803106fc5
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_blackboxing.html b/toolkit/devtools/debugger/test/doc_blackboxing.html
new file mode 100644
index 000000000..a83b16de5
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_breakpoint-move.html b/toolkit/devtools/debugger/test/doc_breakpoint-move.html
new file mode 100644
index 000000000..5124bbbcf
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_breakpoints-break-on-last-line-of-script-on-reload.html b/toolkit/devtools/debugger/test/doc_breakpoints-break-on-last-line-of-script-on-reload.html
new file mode 100644
index 000000000..c1730e506
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_breakpoints-other-tabs.html b/toolkit/devtools/debugger/test/doc_breakpoints-other-tabs.html
new file mode 100644
index 000000000..4273dbdd8
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_breakpoints-reload.html b/toolkit/devtools/debugger/test/doc_breakpoints-reload.html
new file mode 100644
index 000000000..0c6059c6d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_closure-optimized-out.html b/toolkit/devtools/debugger/test/doc_closure-optimized-out.html
new file mode 100644
index 000000000..3ad4e8fc0
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_closures.html b/toolkit/devtools/debugger/test/doc_closures.html
new file mode 100644
index 000000000..1ba91601a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_cmd-break.html b/toolkit/devtools/debugger/test/doc_cmd-break.html
new file mode 100644
index 000000000..4f434746e
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_cmd-dbg.html b/toolkit/devtools/debugger/test/doc_cmd-dbg.html
new file mode 100644
index 000000000..5ab41eb1b
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_conditional-breakpoints.html b/toolkit/devtools/debugger/test/doc_conditional-breakpoints.html
new file mode 100644
index 000000000..7adce7a18
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_domnode-variables.html b/toolkit/devtools/debugger/test/doc_domnode-variables.html
new file mode 100644
index 000000000..9e7531036
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_editor-mode.html b/toolkit/devtools/debugger/test/doc_editor-mode.html
new file mode 100644
index 000000000..8e3573cea
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_empty-tab-01.html b/toolkit/devtools/debugger/test/doc_empty-tab-01.html
new file mode 100644
index 000000000..28398f776
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_empty-tab-02.html b/toolkit/devtools/debugger/test/doc_empty-tab-02.html
new file mode 100644
index 000000000..5db150844
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_event-listeners-01.html b/toolkit/devtools/debugger/test/doc_event-listeners-01.html
new file mode 100644
index 000000000..b44400311
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_event-listeners-02.html b/toolkit/devtools/debugger/test/doc_event-listeners-02.html
new file mode 100644
index 000000000..6a4649de9
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_event-listeners-03.html b/toolkit/devtools/debugger/test/doc_event-listeners-03.html
new file mode 100644
index 000000000..b672a4360
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_frame-parameters.html b/toolkit/devtools/debugger/test/doc_frame-parameters.html
new file mode 100644
index 000000000..b3108d6bf
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_function-display-name.html b/toolkit/devtools/debugger/test/doc_function-display-name.html
new file mode 100644
index 000000000..84e8ce6e1
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_function-search.html b/toolkit/devtools/debugger/test/doc_function-search.html
new file mode 100644
index 000000000..711a873ed
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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() {}
+ let arrow = () => {}
+
+ let foo = bar => {}
+ let foo2 = bar2 = baz2 => 42;
+
+ setTimeout((foo, bar, baz) => {});
+ setTimeout((foo, bar, baz) => 42);
+ </script>
+ </body>
+
+</html>
diff --git a/toolkit/devtools/debugger/test/doc_global-method-override.html b/toolkit/devtools/debugger/test/doc_global-method-override.html
new file mode 100644
index 000000000..d8cf750fc
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_iframes.html b/toolkit/devtools/debugger/test/doc_iframes.html
new file mode 100644
index 000000000..e5a76c280
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_included-script.html b/toolkit/devtools/debugger/test/doc_included-script.html
new file mode 100644
index 000000000..8b134dd42
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_inline-debugger-statement.html b/toolkit/devtools/debugger/test/doc_inline-debugger-statement.html
new file mode 100644
index 000000000..406e9d9da
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_inline-script.html b/toolkit/devtools/debugger/test/doc_inline-script.html
new file mode 100644
index 000000000..d071cc084
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_large-array-buffer.html b/toolkit/devtools/debugger/test/doc_large-array-buffer.html
new file mode 100644
index 000000000..b8545e57c
--- /dev/null
+++ b/toolkit/devtools/debugger/test/doc_large-array-buffer.html
@@ -0,0 +1,27 @@
+<!-- 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 = {};
+
+ for (var i = 0; i < aNumber; i++) {
+ largeObject[i] = aNumber - i - 1;
+ }
+ debugger;
+ }
+ </script>
+ </body>
+</html>
diff --git a/toolkit/devtools/debugger/test/doc_minified.html b/toolkit/devtools/debugger/test/doc_minified.html
new file mode 100644
index 000000000..b229e079f
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_minified_bogus_map.html b/toolkit/devtools/debugger/test/doc_minified_bogus_map.html
new file mode 100644
index 000000000..d6670a7e1
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_native-event-handler.html b/toolkit/devtools/debugger/test/doc_native-event-handler.html
new file mode 100644
index 000000000..cd2a656bf
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_no-page-sources.html b/toolkit/devtools/debugger/test/doc_no-page-sources.html
new file mode 100644
index 000000000..5131578ad
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_pause-exceptions.html b/toolkit/devtools/debugger/test/doc_pause-exceptions.html
new file mode 100644
index 000000000..7766fb49d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_pretty-print-2.html b/toolkit/devtools/debugger/test/doc_pretty-print-2.html
new file mode 100644
index 000000000..509f57d6b
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_pretty-print-3.html b/toolkit/devtools/debugger/test/doc_pretty-print-3.html
new file mode 100644
index 000000000..6192642f3
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_pretty-print-on-paused.html b/toolkit/devtools/debugger/test/doc_pretty-print-on-paused.html
new file mode 100644
index 000000000..a431d0898
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_pretty-print.html b/toolkit/devtools/debugger/test/doc_pretty-print.html
new file mode 100644
index 000000000..dcf595a8d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_promise.html b/toolkit/devtools/debugger/test/doc_promise.html
new file mode 100644
index 000000000..fe6c1d807
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_random-javascript.html b/toolkit/devtools/debugger/test/doc_random-javascript.html
new file mode 100644
index 000000000..69269e409
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_recursion-stack.html b/toolkit/devtools/debugger/test/doc_recursion-stack.html
new file mode 100644
index 000000000..d68fb1d18
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_same-line-functions.html b/toolkit/devtools/debugger/test/doc_same-line-functions.html
new file mode 100644
index 000000000..dbdf2644d
--- /dev/null
+++ b/toolkit/devtools/debugger/test/doc_same-line-functions.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 Tracer test page</title>
+ </head>
+
+ <body>
+ <script src="code_same-line-functions.js"></script>
+ <button onclick="first()">Click me!</button>
+ </body>
+</html>
diff --git a/toolkit/devtools/debugger/test/doc_scope-variable-2.html b/toolkit/devtools/debugger/test/doc_scope-variable-2.html
new file mode 100644
index 000000000..afbfd166a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_scope-variable-3.html b/toolkit/devtools/debugger/test/doc_scope-variable-3.html
new file mode 100644
index 000000000..fcd45cc0a
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_scope-variable-4.html b/toolkit/devtools/debugger/test/doc_scope-variable-4.html
new file mode 100644
index 000000000..17b0e3b10
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_scope-variable.html b/toolkit/devtools/debugger/test/doc_scope-variable.html
new file mode 100644
index 000000000..3fa28fab9
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_script-bookmarklet.html b/toolkit/devtools/debugger/test/doc_script-bookmarklet.html
new file mode 100644
index 000000000..922010062
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_script-eval.html b/toolkit/devtools/debugger/test/doc_script-eval.html
new file mode 100644
index 000000000..7e3f253bb
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_script-switching-01.html b/toolkit/devtools/debugger/test/doc_script-switching-01.html
new file mode 100644
index 000000000..afb4484b5
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_script-switching-02.html b/toolkit/devtools/debugger/test/doc_script-switching-02.html
new file mode 100644
index 000000000..cceeea2c8
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_split-console-paused-reload.html b/toolkit/devtools/debugger/test/doc_split-console-paused-reload.html
new file mode 100644
index 000000000..3848e7a5e
--- /dev/null
+++ b/toolkit/devtools/debugger/test/doc_split-console-paused-reload.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>Test page for opening a split-console when execution is paused</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ function runDebuggerStatement() {
+ debugger;
+ }
+ window.foobar = 1;
+ </script>
+ </body>
+
+</html>
diff --git a/toolkit/devtools/debugger/test/doc_step-out.html b/toolkit/devtools/debugger/test/doc_step-out.html
new file mode 100644
index 000000000..89eda2be1
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_terminate-on-tab-close.html b/toolkit/devtools/debugger/test/doc_terminate-on-tab-close.html
new file mode 100644
index 000000000..2101b3103
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_tracing-01.html b/toolkit/devtools/debugger/test/doc_tracing-01.html
new file mode 100644
index 000000000..be3c7af1b
--- /dev/null
+++ b/toolkit/devtools/debugger/test/doc_tracing-01.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Debugger Tracer test page</title>
+ </head>
+
+ <body>
+ <script src="code_tracing-01.js"></script>
+ <button onclick="main()">Click me!</button>
+
+ <script type="text/javascript">
+ // Have an inline script to make sure the HTML file is listed
+ // in the sources. We want to switch between them.
+ function foo() {
+ return 5;
+ }
+ </script>
+ </body>
+</html>
diff --git a/toolkit/devtools/debugger/test/doc_watch-expression-button.html b/toolkit/devtools/debugger/test/doc_watch-expression-button.html
new file mode 100644
index 000000000..a4a5be26e
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_watch-expressions.html b/toolkit/devtools/debugger/test/doc_watch-expressions.html
new file mode 100644
index 000000000..487b5a5a5
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/doc_with-frame.html b/toolkit/devtools/debugger/test/doc_with-frame.html
new file mode 100644
index 000000000..8fa202b18
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/head.js b/toolkit/devtools/debugger/test/head.js
new file mode 100644
index 000000000..f510a56f7
--- /dev/null
+++ b/toolkit/devtools/debugger/test/head.js
@@ -0,0 +1,1019 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+let { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+// 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.
+let gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
+Services.prefs.setBoolPref("devtools.debugger.log", false);
+
+let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+let { Promise: promise } = Cu.import("resource://gre/modules/devtools/deprecated-sync-thenables.js", {});
+let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
+let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let { require } = devtools;
+let { DevToolsUtils } = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
+let { BrowserToolboxProcess } = Cu.import("resource:///modules/devtools/ToolboxProcess.jsm", {});
+let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
+let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
+let { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
+let EventEmitter = require("devtools/toolkit/event-emitter");
+const { promiseInvoke } = require("devtools/async-utils");
+let TargetFactory = devtools.TargetFactory;
+let Toolbox = devtools.Toolbox;
+
+const EXAMPLE_URL = "http://example.com/browser/browser/devtools/debugger/test/";
+const FRAME_SCRIPT_URL = getRootDirectory(gTestPath) + "code_frame-script.js";
+
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+ gDevTools.testing = false;
+});
+
+// All tests are asynchronous.
+waitForExplicitFinish();
+
+registerCleanupFunction(function* () {
+ 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
+let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
+testDir = testDir.replace(/\/\//g, '/');
+testDir = testDir.replace("chrome:/mochitest", "chrome://mochitest");
+let helpersjs = testDir + "/../../commandline/test/helpers.js";
+Services.scriptloader.loadSubScript(helpersjs, this);
+
+// Redeclare dbg_assert with a fatal behavior.
+function dbg_assert(cond, e) {
+ if (!cond) {
+ throw e;
+ }
+}
+
+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);
+}
+
+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);
+
+ linkedBrowser.addEventListener("load", function onLoad() {
+ linkedBrowser.removeEventListener("load", onLoad, true);
+ info("Tab added and finished loading: " + aUrl);
+ deferred.resolve(tab);
+ }, true);
+
+ return deferred.promise;
+}
+
+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 addAddon(aUrl) {
+ info("Installing addon: " + aUrl);
+
+ let deferred = promise.defer();
+
+ AddonManager.getInstallForURL(aUrl, aInstaller => {
+ aInstaller.install();
+ let listener = {
+ onInstallEnded: function(aAddon, aAddonInstall) {
+ aInstaller.removeListener(listener);
+
+ // Wait for add-on's startup scripts to execute. See bug 997408
+ executeSoon(function() {
+ deferred.resolve(aAddonInstall);
+ });
+ }
+ };
+ aInstaller.addListener(listener);
+ }, "application/x-xpinstall");
+
+ return deferred.promise;
+}
+
+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 getAddonActorForUrl(aClient, aUrl) {
+ info("Get addon actor for URL: " + aUrl);
+ let deferred = promise.defer();
+
+ aClient.listAddons(aResponse => {
+ let addonActor = aResponse.addons.filter(aGrip => aGrip.url == aUrl).pop();
+ info("got addon actor for URL: " + addonActor.actor);
+ 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 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.contains(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.attachment.source.url.contains(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 navigateActiveTabTo(aPanel, aUrl, aWaitForEventName, aEventRepeat) {
+ let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat);
+ let activeTab = aPanel.panelWin.DebuggerController._target.activeTab;
+ aUrl ? activeTab.navigateTo(aUrl) : activeTab.reload();
+ 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) {
+ let deferred = promise.defer();
+
+ aClient.getSources(({sources}) => deferred.resolve(sources));
+
+ return deferred.promise;
+}
+
+function initDebugger(aTarget, aWindow) {
+ info("Initializing a debugger panel.");
+
+ return getTab(aTarget, aWindow).then(aTab => {
+ info("Debugee tab added successfully: " + aTarget);
+
+ let deferred = promise.defer();
+ let debuggee = aTab.linkedBrowser.contentWindow.wrappedJSObject;
+ let target = TargetFactory.forTab(aTab);
+
+ gDevTools.showToolbox(target, "jsdebugger").then(aToolbox => {
+ info("Debugger panel shown successfully.");
+
+ let debuggerPanel = aToolbox.getCurrentPanel();
+ let panelWin = debuggerPanel.panelWin;
+
+ // Wait for the initial resume...
+ panelWin.gClient.addOneTimeListener("resumed", () => {
+ info("Debugger client resumed successfully.");
+
+ prepareDebugger(debuggerPanel);
+ deferred.resolve([aTab, debuggee, debuggerPanel, aWindow]);
+ });
+ });
+
+ return deferred.promise;
+ });
+}
+
+// Creates an add-on debugger for a given add-on. The returned AddonDebugger
+// object must be destroyed before finishing the test
+function initAddonDebugger(aUrl) {
+ let addonDebugger = new AddonDebugger();
+ return addonDebugger.init(aUrl).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*(aUrl) {
+ info("Initializing an addon debugger panel.");
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ 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);
+
+ let connected = promise.defer();
+ this.client.connect(connected.resolve);
+ yield connected.promise;
+
+ let addonActor = yield getAddonActorForUrl(this.client, aUrl);
+
+ let targetOptions = {
+ form: addonActor,
+ client: this.client,
+ chrome: true
+ };
+
+ let toolboxOptions = {
+ customIframe: this.frame
+ };
+
+ this.target = devtools.TargetFactory.forTab(targetOptions);
+ let toolbox = yield gDevTools.showToolbox(this.target, "jsdebugger", devtools.Toolbox.HostType.CUSTOM, toolboxOptions);
+
+ info("Addon debugger panel shown successfully.");
+
+ this.debuggerPanel = toolbox.getCurrentPanel();
+
+ // Wait for the initial resume...
+ yield waitForClientEvents(this.debuggerPanel, "resumed");
+ yield prepareDebugger(this.debuggerPanel);
+ yield this._attachConsole();
+ }),
+
+ destroy: Task.async(function*() {
+ let deferred = promise.defer();
+ this.client.close(deferred.resolve);
+ yield deferred.promise;
+ 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) {
+ 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.FilteredSources._autoSelectFirstItem = true;
+ view.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, aSource = null) {
+ function clickBlackBoxButton() {
+ getBlackBoxButton(aPanel).click();
+ }
+
+ const blackBoxChanged = waitForThreadEvents(aPanel, "blackboxchange");
+
+ if (aSource) {
+ aPanel.panelWin.DebuggerView.Sources.selectedValue = aSource;
+ ensureSourceIs(aPanel, aSource, 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);
+ }
+ },
+ tooltip.defaultShowDelay + 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));
+}
+
+// Tracing helpers
+
+function startTracing(aPanel) {
+ const deferred = promise.defer();
+ aPanel.panelWin.DebuggerController.Tracer.startTracing(aResponse => {
+ if (aResponse.error) {
+ deferred.reject(aResponse);
+ } else {
+ deferred.resolve(aResponse);
+ }
+ });
+ return deferred.promise;
+}
+
+function stopTracing(aPanel) {
+ const deferred = promise.defer();
+ aPanel.panelWin.DebuggerController.Tracer.stopTracing(aResponse => {
+ if (aResponse.error) {
+ deferred.reject(aResponse);
+ } else {
+ deferred.resolve(aResponse);
+ }
+ });
+ return deferred.promise;
+}
+
+function filterTraces(aPanel, f) {
+ const traces = aPanel.panelWin.document
+ .getElementById("tracer-traces")
+ .querySelector("scrollbox")
+ .children;
+ return Array.filter(traces, f);
+}
+function attachAddonActorForUrl(aClient, aUrl) {
+ let deferred = promise.defer();
+
+ getAddonActorForUrl(aClient, aUrl).then(aGrip => {
+ aClient.attachAddon(aGrip.actor, aResponse => {
+ deferred.resolve([aGrip, aResponse]);
+ });
+ });
+
+ return deferred.promise;
+}
+
+function rdpInvoke(aClient, aMethod, ...args) {
+ return promiseInvoke(aClient, aMethod, ...args)
+ .then(({error, message }) => {
+ if (error) {
+ throw new Error(error + ": " + message);
+ }
+ });
+}
+
+function doResume(aPanel) {
+ const threadClient = aPanel.panelWin.gThreadClient;
+ return rdpInvoke(threadClient, threadClient.resume);
+}
+
+function doInterrupt(aPanel) {
+ const threadClient = aPanel.panelWin.gThreadClient;
+ return rdpInvoke(threadClient, 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;
+}
+
+function sendMessageToTab(tab, name, data, objects) {
+ info("Sending message with name " + name + " to tab.");
+
+ tab.linkedBrowser.messageManager.sendAsyncMessage(name, data, objects);
+}
+
+function waitForMessageFromTab(tab, name) {
+ info("Waiting for message with name " + name + " from tab.");
+
+ return new Promise(function (resolve) {
+ let messageManager = tab.linkedBrowser.messageManager;
+ messageManager.addMessageListener(name, function listener(message) {
+ messageManager.removeMessageListener(name, listener);
+ resolve(message);
+ });
+ });
+}
+
+function callInTab(tab, name) {
+ info("Calling function with name " + name + " in tab.");
+
+ sendMessageToTab(tab, "test:call", {
+ name: name,
+ args: Array.prototype.slice.call(arguments, 2)
+ });
+ waitForMessageFromTab(tab, "test:call");
+}
+
+function evalInTab(tab, string) {
+ info("Evalling string " + string + " in tab.");
+
+ sendMessageToTab(tab, "test:eval", {
+ string: string,
+ });
+ waitForMessageFromTab(tab, "test:eval");
+}
+
+function sendMouseClickToTab(tab, target) {
+ info("Sending mouse click to tab.");
+
+ sendMessageToTab(tab, "test:click", undefined, {
+ target: target
+ });
+}
+
+// Source helpers
+
+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.url === aURL);
+ return item && item.value;
+}
+
+function getSourceForm(aSources, aURL) {
+ let item = aSources.getItemByValue(getSourceActor(gSources, aURL));
+ return item.attachment.source;
+}
diff --git a/toolkit/devtools/debugger/test/sjs_random-javascript.sjs b/toolkit/devtools/debugger/test/sjs_random-javascript.sjs
new file mode 100644
index 000000000..3e0ea8e53
--- /dev/null
+++ b/toolkit/devtools/debugger/test/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/toolkit/devtools/debugger/test/testactors.js b/toolkit/devtools/debugger/test/testactors.js
new file mode 100644
index 000000000..01d197927
--- /dev/null
+++ b/toolkit/devtools/debugger/test/testactors.js
@@ -0,0 +1,31 @@
+/* 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");