diff options
Diffstat (limited to 'toolkit/devtools/debugger/test')
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 Binary files differnew file mode 100644 index 000000000..b77ec9531 --- /dev/null +++ b/toolkit/devtools/debugger/test/addon1.xpi diff --git a/toolkit/devtools/debugger/test/addon2.xpi b/toolkit/devtools/debugger/test/addon2.xpi Binary files differnew file mode 100644 index 000000000..460eaca8a --- /dev/null +++ b/toolkit/devtools/debugger/test/addon2.xpi diff --git a/toolkit/devtools/debugger/test/addon3.xpi b/toolkit/devtools/debugger/test/addon3.xpi Binary files differnew file mode 100644 index 000000000..673b31b9d --- /dev/null +++ b/toolkit/devtools/debugger/test/addon3.xpi diff --git a/toolkit/devtools/debugger/test/addon4.xpi b/toolkit/devtools/debugger/test/addon4.xpi Binary files differnew file mode 100644 index 000000000..56dc98f6e --- /dev/null +++ b/toolkit/devtools/debugger/test/addon4.xpi diff --git a/toolkit/devtools/debugger/test/addon5.xpi b/toolkit/devtools/debugger/test/addon5.xpi Binary files differnew file mode 100644 index 000000000..16991f7a0 --- /dev/null +++ b/toolkit/devtools/debugger/test/addon5.xpi 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"); |