summaryrefslogtreecommitdiff
path: root/toolkit/devtools/debugger/test
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/devtools/debugger/test')
-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
349 files changed, 30834 insertions, 0 deletions
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");