diff options
Diffstat (limited to 'devtools/client/debugger/test')
420 files changed, 34908 insertions, 0 deletions
diff --git a/devtools/client/debugger/test/.eslintrc.js b/devtools/client/debugger/test/.eslintrc.js new file mode 100644 index 000000000..8d15a76d9 --- /dev/null +++ b/devtools/client/debugger/test/.eslintrc.js @@ -0,0 +1,6 @@ +"use strict"; + +module.exports = { + // Extend from the shared list of defined globals for mochitests. + "extends": "../../../.eslintrc.mochitests.js" +}; diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon3/lib/main.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon3/lib/main.js new file mode 100644 index 000000000..fc00b60a1 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon3/lib/main.js @@ -0,0 +1,13 @@ +var { Cc, Ci } = require("chrome"); +var { once } = require("sdk/system/events"); + +var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); +var observer = { + observe: function () { + debugger; + } +}; + +once("sdk:loader:destroy", () => observerService.removeObserver(observer, "debuggerAttached")); + +observerService.addObserver(observer, "debuggerAttached", false); diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon3/package.json b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon3/package.json new file mode 100644 index 000000000..4bf1bed50 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon3/package.json @@ -0,0 +1,9 @@ +{ + "name": "browser_dbg_addon3", + "title": "browser_dbg_addon3", + "id": "jid1-ami3akps3baaeg", + "description": "a basic add-on", + "author": "", + "license": "MPL 2.0", + "version": "0.1" +} diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/bootstrap.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/bootstrap.js new file mode 100644 index 000000000..e8bb9fcce --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/bootstrap.js @@ -0,0 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var { interfaces: Ci, utils: Cu } = Components; + +function notify() { + // Log objects so makeDebuggeeValue can get the global to use + console.log({ msg: "Hello again" }); +} + +function startup(aParams, aReason) { + const { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); + let res = Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + res.setSubstitution("browser_dbg_addon4", aParams.resourceURI); + + // Load a JS module + Cu.import("resource://browser_dbg_addon4/test.jsm"); // eslint-disable-line mozilla/no-single-arg-cu-import + // Log objects so makeDebuggeeValue can get the global to use + console.log({ msg: "Hello from the test add-on" }); + + Services.obs.addObserver(notify, "addon-test-ping", false); +} + +function shutdown(aParams, aReason) { + Services.obs.removeObserver(notify, "addon-test-ping"); + + // Unload the JS module + Cu.unload("resource://browser_dbg_addon4/test.jsm"); + + let res = Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + res.setSubstitution("browser_dbg_addon4", null); +} diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/chrome.manifest b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/chrome.manifest new file mode 100644 index 000000000..ccb88ddf1 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/chrome.manifest @@ -0,0 +1 @@ +content browser_dbg_addon4 . diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/install.rdf b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/install.rdf new file mode 100644 index 000000000..45679ffc9 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/install.rdf @@ -0,0 +1,19 @@ +<?xml version="1.0"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:install-manifest"> + <em:id>browser_dbg_addon4@tests.mozilla.org</em:id> + <em:version>1.0</em:version> + <em:name>Test add-on with JS Modules</em:name> + <em:bootstrap>true</em:bootstrap> + <em:targetApplication> + <Description> + <em:id>toolkit@mozilla.org</em:id> + <em:minVersion>0</em:minVersion> + <em:maxVersion>*</em:maxVersion> + </Description> + </em:targetApplication> + </Description> +</RDF> diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test.jsm b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test.jsm new file mode 100644 index 000000000..17bebfd8e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test.jsm @@ -0,0 +1,6 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const EXPORTED_SYMBOLS = ["Foo"]; + +const Foo = {}; diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test.xul b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test.xul new file mode 100644 index 000000000..733817ad8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test.xul @@ -0,0 +1,8 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="text/javascript" src="testxul.js"/> + <label value="test.xul"/> +</window> diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test2.jsm b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test2.jsm new file mode 100644 index 000000000..703869f43 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test2.jsm @@ -0,0 +1,6 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const EXPORTED_SYMBOLS = ["Bar"]; + +const Bar = {}; diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test2.xul b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test2.xul new file mode 100644 index 000000000..372d05587 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/test2.xul @@ -0,0 +1,8 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="text/javascript" src="testxul2.js"/> + <label value="test2.xul"/> +</window> diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/testxul.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/testxul.js new file mode 100644 index 000000000..30ad9d2f8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/testxul.js @@ -0,0 +1,4 @@ +// Define something in here or the script may get collected +window.addEventListener("unload", function () { + window.foo = "bar"; +}); diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/testxul2.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/testxul2.js new file mode 100644 index 000000000..30ad9d2f8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon4/testxul2.js @@ -0,0 +1,4 @@ +// Define something in here or the script may get collected +window.addEventListener("unload", function () { + window.foo = "bar"; +}); diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/bootstrap.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/bootstrap.js new file mode 100644 index 000000000..8edc53756 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/bootstrap.js @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var { interfaces: Ci, classes: Cc } = Components; + +function startup(aParams, aReason) { + Components.utils.import("resource://gre/modules/Services.jsm"); + let res = Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + res.setSubstitution("browser_dbg_addon5", aParams.resourceURI); + + // Load a JS module + Components.utils.import("resource://browser_dbg_addon5/test.jsm"); +} + +function shutdown(aParams, aReason) { + // Unload the JS module + Components.utils.unload("resource://browser_dbg_addon5/test.jsm"); + + let res = Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); + res.setSubstitution("browser_dbg_addon5", null); +} diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/chrome.manifest b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/chrome.manifest new file mode 100644 index 000000000..ceef8d06d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/chrome.manifest @@ -0,0 +1 @@ +content browser_dbg_addon5 . diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/install.rdf b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/install.rdf new file mode 100644 index 000000000..af2cbbb5d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/install.rdf @@ -0,0 +1,20 @@ +<?xml version="1.0"?> + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + + <Description about="urn:mozilla:install-manifest"> + <em:id>browser_dbg_addon5@tests.mozilla.org</em:id> + <em:version>1.0</em:version> + <em:name>Test unpacked add-on with JS Modules</em:name> + <em:bootstrap>true</em:bootstrap> + <em:unpack>true</em:unpack> + <em:targetApplication> + <Description> + <em:id>toolkit@mozilla.org</em:id> + <em:minVersion>0</em:minVersion> + <em:maxVersion>*</em:maxVersion> + </Description> + </em:targetApplication> + </Description> +</RDF> diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test.jsm b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test.jsm new file mode 100644 index 000000000..17bebfd8e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test.jsm @@ -0,0 +1,6 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const EXPORTED_SYMBOLS = ["Foo"]; + +const Foo = {}; diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test.xul b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test.xul new file mode 100644 index 000000000..733817ad8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test.xul @@ -0,0 +1,8 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="text/javascript" src="testxul.js"/> + <label value="test.xul"/> +</window> diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test2.jsm b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test2.jsm new file mode 100644 index 000000000..703869f43 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test2.jsm @@ -0,0 +1,6 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const EXPORTED_SYMBOLS = ["Bar"]; + +const Bar = {}; diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test2.xul b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test2.xul new file mode 100644 index 000000000..372d05587 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/test2.xul @@ -0,0 +1,8 @@ +<?xml version="1.0"?> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script type="text/javascript" src="testxul2.js"/> + <label value="test2.xul"/> +</window> diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/testxul.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/testxul.js new file mode 100644 index 000000000..30ad9d2f8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/testxul.js @@ -0,0 +1,4 @@ +// Define something in here or the script may get collected +window.addEventListener("unload", function () { + window.foo = "bar"; +}); diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/testxul2.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/testxul2.js new file mode 100644 index 000000000..30ad9d2f8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon5/testxul2.js @@ -0,0 +1,4 @@ +// Define something in here or the script may get collected +window.addEventListener("unload", function () { + window.foo = "bar"; +}); diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon_webext_contentscript/manifest.json b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon_webext_contentscript/manifest.json new file mode 100644 index 000000000..ebc834bf7 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon_webext_contentscript/manifest.json @@ -0,0 +1,18 @@ +{ + "manifest_version": 2, + "name": "test content script sources", + "description": "test content script sources", + "version": "0.1.0", + "applications": { + "gecko": { + "id": "test-contentscript-sources@mozilla.com" + } + }, + "content_scripts": [ + { + "matches": ["<all_urls>"], + "js": ["webext-content-script.js"], + "run_at": "document_start" + } + ] +} diff --git a/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon_webext_contentscript/webext-content-script.js b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon_webext_contentscript/webext-content-script.js new file mode 100644 index 000000000..591c78840 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-source/browser_dbg_addon_webext_contentscript/webext-content-script.js @@ -0,0 +1 @@ +console.log("CONTENT SCRIPT LOADED"); diff --git a/devtools/client/debugger/test/mochitest/addon-webext-contentscript.xpi b/devtools/client/debugger/test/mochitest/addon-webext-contentscript.xpi Binary files differnew file mode 100644 index 000000000..9e61dec1d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon-webext-contentscript.xpi diff --git a/devtools/client/debugger/test/mochitest/addon1.xpi b/devtools/client/debugger/test/mochitest/addon1.xpi Binary files differnew file mode 100644 index 000000000..689689ebe --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon1.xpi diff --git a/devtools/client/debugger/test/mochitest/addon2.xpi b/devtools/client/debugger/test/mochitest/addon2.xpi Binary files differnew file mode 100644 index 000000000..8f6ec6dc1 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon2.xpi diff --git a/devtools/client/debugger/test/mochitest/addon3.xpi b/devtools/client/debugger/test/mochitest/addon3.xpi Binary files differnew file mode 100644 index 000000000..b22fc3da7 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon3.xpi diff --git a/devtools/client/debugger/test/mochitest/addon4.xpi b/devtools/client/debugger/test/mochitest/addon4.xpi Binary files differnew file mode 100644 index 000000000..1f6f106f3 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon4.xpi diff --git a/devtools/client/debugger/test/mochitest/addon5.xpi b/devtools/client/debugger/test/mochitest/addon5.xpi Binary files differnew file mode 100644 index 000000000..56b38761d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/addon5.xpi diff --git a/devtools/client/debugger/test/mochitest/browser.ini b/devtools/client/debugger/test/mochitest/browser.ini new file mode 100644 index 000000000..832addf89 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser.ini @@ -0,0 +1,317 @@ +# Tests in this directory are split into two manifests (this and browser2.ini) +# to facilitate better chunking; see bug 1294489. + +[DEFAULT] +tags = devtools +subsuite = devtools +skip-if = (os == 'linux' && debug && bits == 32) +support-files = + addon1.xpi + addon2.xpi + addon3.xpi + addon4.xpi + addon5.xpi + addon-webext-contentscript.xpi + addon-source/browser_dbg_addon5/* + code_binary_search.coffee + code_binary_search.js + code_binary_search.map + code_blackboxing_blackboxme.js + code_blackboxing_one.js + code_blackboxing_three.js + code_blackboxing_two.js + code_blackboxing_unblackbox.min.js + code_breakpoints-break-on-last-line-of-script-on-reload.js + code_breakpoints-other-tabs.js + code_bug-896139.js + code_frame-script.js + code_function-jump-01.js + code_function-search-01.js + code_function-search-02.js + code_function-search-03.js + code_location-changes.js + code_listworkers-worker1.js + code_listworkers-worker2.js + code_math.js + code_math.map + code_math.min.js + code_math_bogus_map.js + code_same-line-functions.js + code_script-eval.js + code_script-switching-01.js + code_script-switching-02.js + code_test-editor-mode + code_ugly.js + code_ugly-2.js + code_ugly-3.js + code_ugly-4.js + code_ugly-5.js + code_ugly-6.js + code_ugly-7.js + code_ugly-8 + code_ugly-8^headers^ + code_worker-source-map.coffee + code_worker-source-map.js + code_worker-source-map.js.map + code_WorkerActor.attach-worker1.js + code_WorkerActor.attach-worker2.js + code_WorkerActor.attachThread-worker.js + doc_auto-pretty-print-01.html + doc_auto-pretty-print-02.html + doc_binary_search.html + doc_blackboxing.html + doc_blackboxing_unblackbox.html + doc_breakpoints-break-on-last-line-of-script-on-reload.html + doc_breakpoints-other-tabs.html + doc_breakpoints-reload.html + doc_bug-896139.html + doc_closures.html + doc_closure-optimized-out.html + doc_cmd-break.html + doc_cmd-dbg.html + doc_breakpoint-move.html + doc_conditional-breakpoints.html + doc_domnode-variables.html + doc_editor-mode.html + doc_empty-tab-01.html + doc_empty-tab-02.html + doc_event-listeners-01.html + doc_event-listeners-02.html + doc_event-listeners-03.html + doc_event-listeners-04.html + doc_frame-parameters.html + doc_function-display-name.html + doc_function-jump.html + doc_function-search.html + doc_global-method-override.html + doc_iframes.html + doc_included-script.html + doc_inline-debugger-statement.html + doc_inline-script.html + doc_large-array-buffer.html + doc_listworkers-tab.html + doc_map-set.html + doc_minified.html + doc_minified_bogus_map.html + doc_native-event-handler.html + doc_no-page-sources.html + doc_pause-exceptions.html + doc_pretty-print.html + doc_pretty-print-2.html + doc_pretty-print-3.html + doc_pretty-print-on-paused.html + doc_promise-get-allocation-stack.html + doc_promise-get-fulfillment-stack.html + doc_promise-get-rejection-stack.html + doc_promise.html + doc_proxy.html + doc_random-javascript.html + doc_recursion-stack.html + doc_scope-variable.html + doc_scope-variable-2.html + doc_scope-variable-3.html + doc_scope-variable-4.html + doc_script-eval.html + doc_script-bookmarklet.html + doc_script-switching-01.html + doc_script-switching-02.html + doc_script_webext_contentscript.html + doc_split-console-paused-reload.html + doc_step-many-statements.html + doc_step-out.html + doc_terminate-on-tab-close.html + doc_watch-expressions.html + doc_watch-expression-button.html + doc_whitespace-property-names.html + doc_with-frame.html + doc_worker-source-map.html + doc_WorkerActor.attach-tab1.html + doc_WorkerActor.attach-tab2.html + doc_WorkerActor.attachThread-tab.html + head.js + sjs_post-page.sjs + sjs_random-javascript.sjs + testactors.js + !/devtools/client/commandline/test/helpers.js + !/devtools/client/framework/test/shared-head.js + +[browser_dbg_aaa_run_first_leaktest.js] +skip-if = e10s && debug +[browser_dbg_addonactor.js] +tags = addons +[browser_dbg_addon-sources.js] +tags = addons +[browser_dbg_addon-workers-dbg-enabled.js] +tags = addons +[browser_dbg_addon-modules.js] +skip-if = e10s # TODO +tags = addons +[browser_dbg_addon-modules-unpacked.js] +skip-if = e10s # TODO +tags = addons +[browser_dbg_addon-panels.js] +tags = addons +[browser_dbg_addon-console.js] +skip-if = e10s && debug || os == 'win' # bug 1005274 +tags = addons +[browser_dbg_auto-pretty-print-01.js] +[browser_dbg_auto-pretty-print-02.js] +[browser_dbg_auto-pretty-print-03.js] +[browser_dbg_bfcache.js] +skip-if = e10s || true # bug 1113935 +[browser_dbg_blackboxing-01.js] +[browser_dbg_blackboxing-02.js] +[browser_dbg_blackboxing-03.js] +[browser_dbg_blackboxing-04.js] +[browser_dbg_blackboxing-05.js] +[browser_dbg_blackboxing-06.js] +[browser_dbg_blackboxing-07.js] +[browser_dbg_breadcrumbs-access.js] +[browser_dbg_break-in-anon.js] +[browser_dbg_break-on-next.js] +[browser_dbg_break-on-next-console.js] +[browser_dbg_break-on-dom-01.js] +[browser_dbg_break-on-dom-02.js] +[browser_dbg_break-on-dom-03.js] +[browser_dbg_break-on-dom-04.js] +[browser_dbg_break-on-dom-05.js] +[browser_dbg_break-on-dom-06.js] +[browser_dbg_break-on-dom-07.js] +[browser_dbg_break-on-dom-08.js] +[browser_dbg_break-on-dom-event-01.js] +skip-if = e10s || os == "mac" || e10s # Bug 895426 +[browser_dbg_break-on-dom-event-02.js] +skip-if = e10s # TODO +[browser_dbg_break-on-dom-event-03.js] +skip-if = e10s # TODO +[browser_dbg_break-unselected.js] +[browser_dbg_breakpoints-actual-location.js] +[browser_dbg_breakpoints-actual-location2.js] +[browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js] +skip-if = e10s # Bug 1093535 +[browser_dbg_breakpoints-button-01.js] +[browser_dbg_breakpoints-button-02.js] +[browser_dbg_breakpoints-condition-thrown-message.js] +skip-if = e10s && debug +[browser_dbg_breakpoints-contextmenu-add.js] +[browser_dbg_breakpoints-contextmenu.js] +[browser_dbg_breakpoints-disabled-reload.js] +skip-if = e10s # Bug 1093535 +[browser_dbg_breakpoints-editor.js] +skip-if = e10s && debug +[browser_dbg_breakpoints-eval.js] +skip-if = e10s && debug +[browser_dbg_breakpoints-highlight.js] +skip-if = e10s && debug +[browser_dbg_breakpoints-new-script.js] +skip-if = e10s && debug +[browser_dbg_breakpoints-other-tabs.js] +skip-if = e10s && debug +[browser_dbg_breakpoints-pane.js] +skip-if = e10s && debug +[browser_dbg_breakpoints-reload.js] +skip-if = e10s && debug +[browser_dbg_bug-896139.js] +skip-if = e10s && debug +[browser_dbg_chrome-create.js] +skip-if = e10s && debug +[browser_dbg_chrome-debugging.js] +skip-if = e10s && debug +[browser_dbg_clean-exit-window.js] +skip-if = true # Bug 933950 (leaky test) +[browser_dbg_clean-exit.js] +skip-if = true # Bug 1044985 (racy test) +[browser_dbg_closure-inspection.js] +skip-if = e10s && debug +[browser_dbg_cmd-blackbox.js] +skip-if = e10s && debug +[browser_dbg_cmd-break.js] +skip-if = e10s # TODO +[browser_dbg_cmd-dbg.js] +skip-if = e10s # TODO +[browser_dbg_conditional-breakpoints-01.js] +skip-if = e10s && debug +[browser_dbg_conditional-breakpoints-02.js] +skip-if = e10s && debug +[browser_dbg_conditional-breakpoints-03.js] +skip-if = e10s && debug +[browser_dbg_conditional-breakpoints-04.js] +skip-if = e10s && debug +[browser_dbg_conditional-breakpoints-05.js] +skip-if = e10s && debug +[browser_dbg_console-eval.js] +skip-if = e10s && debug +[browser_dbg_console-named-eval.js] +skip-if = e10s && debug +[browser_dbg_server-conditional-bp-01.js] +skip-if = e10s && debug +[browser_dbg_server-conditional-bp-02.js] +skip-if = e10s && debug +[browser_dbg_server-conditional-bp-03.js] +skip-if = e10s && debug +[browser_dbg_server-conditional-bp-04.js] +skip-if = e10s && debug +[browser_dbg_server-conditional-bp-05.js] +skip-if = e10s && debug +[browser_dbg_controller-evaluate-01.js] +skip-if = e10s && debug +[browser_dbg_controller-evaluate-02.js] +skip-if = e10s && debug +[browser_dbg_debugger-statement.js] +skip-if = e10s && debug +[browser_dbg_editor-contextmenu.js] +skip-if = e10s && debug +[browser_dbg_editor-mode.js] +skip-if = e10s && debug +[browser_dbg_event-listeners-01.js] +skip-if = e10s && debug +[browser_dbg_event-listeners-02.js] +skip-if = e10s && debug +[browser_dbg_event-listeners-03.js] +skip-if = e10s && debug +[browser_dbg_event-listeners-04.js] +skip-if = debug || e10s # debug bug 1142597, e10s bug 1146603. +[browser_dbg_file-reload.js] +skip-if = e10s && debug +[browser_dbg_function-display-name.js] +skip-if = e10s && debug +[browser_dbg_global-method-override.js] +skip-if = e10s && debug +[browser_dbg_globalactor.js] +skip-if = e10s # TODO +[browser_dbg_hide-toolbar-buttons.js] +skip-if = e10s +[browser_dbg_host-layout.js] +skip-if = e10s && debug +[browser_dbg_jump-to-function-definition.js] +skip-if = e10s && debug +[browser_dbg_iframes.js] +skip-if = e10s # TODO +[browser_dbg_instruments-pane-collapse.js] +skip-if = e10s && debug +[browser_dbg_instruments-pane-collapse_keyboard.js] +skip-if = (os == 'mac' && e10s && debug) # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control +[browser_dbg_interrupts.js] +skip-if = e10s && debug +[browser_dbg_listaddons.js] +skip-if = e10s && debug +tags = addons +[browser_dbg_listtabs-01.js] +skip-if = e10s # TODO +[browser_dbg_listtabs-02.js] +skip-if = true # Never worked for remote frames, needs a mock DebuggerServerConnection +[browser_dbg_listtabs-03.js] +skip-if = e10s && debug +[browser_dbg_listworkers.js] +[browser_dbg_location-changes-01-simple.js] +skip-if = e10s && debug +[browser_dbg_location-changes-02-blank.js] +skip-if = e10s && debug +[browser_dbg_location-changes-03-new.js] +skip-if = e10s # TODO +[browser_dbg_location-changes-04-breakpoint.js] +skip-if = e10s # TODO +[browser_dbg_multiple-windows.js] +skip-if = e10s # TODO +[browser_dbg_navigation.js] +skip-if = e10s && debug diff --git a/devtools/client/debugger/test/mochitest/browser2.ini b/devtools/client/debugger/test/mochitest/browser2.ini new file mode 100644 index 000000000..193c510ff --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser2.ini @@ -0,0 +1,460 @@ +# Tests in this directory are split into two manifests (this and browser.ini) +# to facilitate better chunking; see bug 1294489. + +[DEFAULT] +tags = devtools +subsuite = devtools +skip-if = (os == 'linux' && debug && bits == 32) +support-files = + addon1.xpi + addon2.xpi + addon3.xpi + addon4.xpi + addon5.xpi + addon-webext-contentscript.xpi + addon-source/browser_dbg_addon5/* + code_binary_search.coffee + code_binary_search.js + code_binary_search.map + code_blackboxing_blackboxme.js + code_blackboxing_one.js + code_blackboxing_three.js + code_blackboxing_two.js + code_blackboxing_unblackbox.min.js + code_breakpoints-break-on-last-line-of-script-on-reload.js + code_breakpoints-other-tabs.js + code_bug-896139.js + code_frame-script.js + code_function-jump-01.js + code_function-search-01.js + code_function-search-02.js + code_function-search-03.js + code_location-changes.js + code_listworkers-worker1.js + code_listworkers-worker2.js + code_math.js + code_math.map + code_math.min.js + code_math_bogus_map.js + code_same-line-functions.js + code_script-eval.js + code_script-switching-01.js + code_script-switching-02.js + code_test-editor-mode + code_ugly.js + code_ugly-2.js + code_ugly-3.js + code_ugly-4.js + code_ugly-5.js + code_ugly-6.js + code_ugly-7.js + code_ugly-8 + code_ugly-8^headers^ + code_worker-source-map.coffee + code_worker-source-map.js + code_worker-source-map.js.map + code_WorkerActor.attach-worker1.js + code_WorkerActor.attach-worker2.js + code_WorkerActor.attachThread-worker.js + doc_auto-pretty-print-01.html + doc_auto-pretty-print-02.html + doc_binary_search.html + doc_blackboxing.html + doc_blackboxing_unblackbox.html + doc_breakpoints-break-on-last-line-of-script-on-reload.html + doc_breakpoints-other-tabs.html + doc_breakpoints-reload.html + doc_bug-896139.html + doc_closures.html + doc_closure-optimized-out.html + doc_cmd-break.html + doc_cmd-dbg.html + doc_breakpoint-move.html + doc_conditional-breakpoints.html + doc_domnode-variables.html + doc_editor-mode.html + doc_empty-tab-01.html + doc_empty-tab-02.html + doc_event-listeners-01.html + doc_event-listeners-02.html + doc_event-listeners-03.html + doc_event-listeners-04.html + doc_frame-parameters.html + doc_function-display-name.html + doc_function-jump.html + doc_function-search.html + doc_global-method-override.html + doc_iframes.html + doc_included-script.html + doc_inline-debugger-statement.html + doc_inline-script.html + doc_large-array-buffer.html + doc_listworkers-tab.html + doc_map-set.html + doc_minified.html + doc_minified_bogus_map.html + doc_native-event-handler.html + doc_no-page-sources.html + doc_pause-exceptions.html + doc_pretty-print.html + doc_pretty-print-2.html + doc_pretty-print-3.html + doc_pretty-print-on-paused.html + doc_promise-get-allocation-stack.html + doc_promise-get-fulfillment-stack.html + doc_promise-get-rejection-stack.html + doc_promise.html + doc_proxy.html + doc_random-javascript.html + doc_recursion-stack.html + doc_scope-variable.html + doc_scope-variable-2.html + doc_scope-variable-3.html + doc_scope-variable-4.html + doc_script-eval.html + doc_script-bookmarklet.html + doc_script-switching-01.html + doc_script-switching-02.html + doc_script_webext_contentscript.html + doc_split-console-paused-reload.html + doc_step-many-statements.html + doc_step-out.html + doc_terminate-on-tab-close.html + doc_watch-expressions.html + doc_watch-expression-button.html + doc_whitespace-property-names.html + doc_with-frame.html + doc_worker-source-map.html + doc_WorkerActor.attach-tab1.html + doc_WorkerActor.attach-tab2.html + doc_WorkerActor.attachThread-tab.html + head.js + sjs_post-page.sjs + sjs_random-javascript.sjs + testactors.js + !/devtools/client/commandline/test/helpers.js + !/devtools/client/framework/test/shared-head.js + +[browser_dbg_no-dangling-breakpoints.js] +skip-if = e10s && debug +[browser_dbg_no-page-sources.js] +skip-if = e10s && debug +[browser_dbg_on-pause-highlight.js] +skip-if = e10s && debug +[browser_dbg_on-pause-raise.js] +skip-if = e10s && debug || os == "linux" # Bug 888811 & bug 891176 +[browser_dbg_optimized-out-vars.js] +skip-if = e10s && debug +[browser_dbg_panel-size.js] +skip-if = e10s && debug +[browser_dbg_parser-01.js] +skip-if = e10s && debug +[browser_dbg_parser-02.js] +skip-if = e10s && debug +[browser_dbg_parser-03.js] +skip-if = e10s && debug +[browser_dbg_parser-04.js] +skip-if = e10s && debug +[browser_dbg_parser-05.js] +skip-if = e10s && debug +[browser_dbg_parser-06.js] +skip-if = e10s && debug +[browser_dbg_parser-07.js] +skip-if = e10s && debug +[browser_dbg_parser-08.js] +skip-if = e10s && debug +[browser_dbg_parser-09.js] +skip-if = e10s && debug +[browser_dbg_parser-10.js] +skip-if = e10s && debug +[browser_dbg_parser-11.js] +[browser_dbg_parser-computed-name.js] +[browser_dbg_parser-function-defaults.js] +[browser_dbg_parser-spread-expression.js] +[browser_dbg_parser-template-strings.js] +skip-if = e10s && debug +[browser_dbg_pause-exceptions-01.js] +skip-if = e10s && debug +[browser_dbg_pause-exceptions-02.js] +skip-if = e10s && debug +[browser_dbg_pause-no-step.js] +skip-if = e10s && debug +[browser_dbg_pause-resume.js] +skip-if = e10s && debug +[browser_dbg_pause-warning.js] +skip-if = e10s && debug +[browser_dbg_paused-keybindings.js] +skip-if = e10s +[browser_dbg_post-page.js] +[browser_dbg_pretty-print-01.js] +skip-if = e10s && debug +[browser_dbg_pretty-print-02.js] +skip-if = e10s && debug +[browser_dbg_pretty-print-03.js] +skip-if = e10s && debug +[browser_dbg_pretty-print-04.js] +skip-if = e10s && debug +[browser_dbg_pretty-print-05.js] +skip-if = e10s && debug +[browser_dbg_pretty-print-06.js] +skip-if = e10s && debug +[browser_dbg_pretty-print-07.js] +skip-if = e10s && debug +[browser_dbg_pretty-print-08.js] +skip-if = e10s && debug +[browser_dbg_pretty-print-09.js] +skip-if = e10s && debug +[browser_dbg_pretty-print-10.js] +skip-if = e10s && debug +[browser_dbg_pretty-print-11.js] +skip-if = e10s && debug +[browser_dbg_pretty-print-12.js] +skip-if = e10s && debug +[browser_dbg_pretty-print-13.js] +skip-if = e10s && debug +[browser_dbg_pretty-print-on-paused.js] +skip-if = e10s && debug +[browser_dbg_progress-listener-bug.js] +skip-if = e10s && debug +[browser_dbg_promises-allocation-stack.js] +skip-if = e10s && debug +[browser_dbg_promises-chrome-allocation-stack.js] +skip-if = true # Bug 1177730 +[browser_dbg_promises-fulfillment-stack.js] +skip-if = e10s && debug +[browser_dbg_promises-rejection-stack.js] +skip-if = e10s && debug +[browser_dbg_reload-preferred-script-02.js] +skip-if = e10s && debug +[browser_dbg_reload-preferred-script-03.js] +skip-if = e10s && debug +[browser_dbg_reload-same-script.js] +skip-if = e10s && debug +[browser_dbg_scripts-switching-01.js] +skip-if = e10s && debug +[browser_dbg_scripts-switching-02.js] +skip-if = e10s && debug +[browser_dbg_scripts-switching-03.js] +skip-if = e10s && debug +[browser_dbg_search-autofill-identifier.js] +skip-if = e10s && debug +[browser_dbg_search-basic-01.js] +skip-if = e10s && debug +[browser_dbg_search-basic-02.js] +skip-if = e10s && debug +[browser_dbg_search-basic-03.js] +skip-if = e10s && debug +[browser_dbg_search-basic-04.js] +skip-if = e10s && debug +[browser_dbg_search-global-01.js] +skip-if = e10s && debug +[browser_dbg_search-global-02.js] +skip-if = e10s && debug +[browser_dbg_search-global-03.js] +skip-if = e10s # Bug 1093535 +[browser_dbg_search-global-04.js] +skip-if = e10s && debug +[browser_dbg_search-global-05.js] +skip-if = e10s && debug +[browser_dbg_search-global-06.js] +skip-if = e10s && debug +[browser_dbg_search-popup-jank.js] +skip-if = e10s && debug +[browser_dbg_search-sources-01.js] +skip-if = e10s && debug +[browser_dbg_search-sources-02.js] +skip-if = e10s && debug +[browser_dbg_search-sources-03.js] +skip-if = e10s && debug +[browser_dbg_search-symbols.js] +skip-if = (e10s && debug) || os == "linux" # Bug 1132375 +[browser_dbg_searchbox-help-popup-01.js] +skip-if = e10s && debug +[browser_dbg_searchbox-help-popup-02.js] +skip-if = e10s && debug +[browser_dbg_searchbox-parse.js] +skip-if = (debug) || (os == 'linux' && asan) # asan, bug 1313861, debug: bug 1313861 +[browser_dbg_source-maps-01.js] +skip-if = e10s && debug +[browser_dbg_source-maps-02.js] +skip-if = e10s && debug +[browser_dbg_source-maps-03.js] +skip-if = e10s && debug +[browser_dbg_source-maps-04.js] +skip-if = e10s # Bug 1093535 +[browser_dbg_sources-cache.js] +[browser_dbg_sources-contextmenu-01.js] +subsuite = clipboard +[browser_dbg_sources-contextmenu-02.js] +skip-if = e10s && debug +[browser_dbg_sources-eval-01.js] +skip-if = true # non-named eval sources turned off for now, bug 1124106 +[browser_dbg_sources-eval-02.js] +[browser_dbg_sources-iframe-reload.js] +[browser_dbg_sources-keybindings.js] +subsuite = clipboard +skip-if = e10s && debug +[browser_dbg_sources-labels.js] +skip-if = e10s && debug +[browser_dbg_sources-large.js] +[browser_dbg_sources-sorting.js] +skip-if = e10s && debug +[browser_dbg_sources-bookmarklet.js] +skip-if = e10s && debug +[browser_dbg_sources-webext-contentscript.js] +[browser_dbg_split-console-paused-reload.js] +skip-if = true # Bug 1288348 - previously e10s && debug +[browser_dbg_stack-01.js] +skip-if = e10s && debug +[browser_dbg_stack-02.js] +skip-if = e10s && debug +[browser_dbg_stack-03.js] +skip-if = e10s # TODO +[browser_dbg_stack-04.js] +skip-if = e10s && debug +[browser_dbg_stack-05.js] +skip-if = e10s && (debug || asan) # timeouts +[browser_dbg_stack-06.js] +skip-if = e10s && debug +[browser_dbg_stack-07.js] +skip-if = e10s && debug +[browser_dbg_stack-contextmenu-01.js] +skip-if = e10s && debug +[browser_dbg_stack-contextmenu-02.js] +subsuite = clipboard +skip-if = e10s && debug +[browser_dbg_step-out.js] +skip-if = e10s && debug +[browser_dbg_tabactor-01.js] +skip-if = e10s # TODO +[browser_dbg_tabactor-02.js] +skip-if = e10s # TODO +[browser_dbg_terminate-on-tab-close.js] +skip-if = e10s && debug +[browser_dbg_variables-view-01.js] +skip-if = e10s && debug +[browser_dbg_variables-view-02.js] +skip-if = e10s && debug +[browser_dbg_variables-view-03.js] +skip-if = e10s && debug +[browser_dbg_variables-view-04.js] +skip-if = e10s && debug +[browser_dbg_variables-view-05.js] +skip-if = e10s && debug +[browser_dbg_variables-view-06.js] +skip-if = e10s && debug +[browser_dbg_variables-view-07.js] +skip-if = e10s && debug +[browser_dbg_variables-view-08.js] +skip-if = e10s && debug +[browser_dbg_variables-view-accessibility.js] +subsuite = clipboard +skip-if = e10s && debug +[browser_dbg_variables-view-data.js] +skip-if = e10s && debug +[browser_dbg_variables-view-edit-cancel.js] +skip-if = e10s && debug +[browser_dbg_variables-view-edit-click.js] +skip-if = e10s || (os == 'mac' || os == 'win') && (debug == false) # Bug 986166 +[browser_dbg_variables-view-edit-getset-01.js] +skip-if = e10s && debug +[browser_dbg_variables-view-edit-getset-02.js] +skip-if = e10s && debug +[browser_dbg_variables-view-edit-value.js] +skip-if = e10s && debug +[browser_dbg_variables-view-edit-watch.js] +skip-if = e10s && debug +[browser_dbg_variables-view-filter-01.js] +skip-if = e10s && debug +[browser_dbg_variables-view-filter-02.js] +skip-if = e10s && debug +[browser_dbg_variables-view-filter-03.js] +skip-if = e10s && debug +[browser_dbg_variables-view-filter-04.js] +skip-if = e10s && debug +[browser_dbg_variables-view-filter-05.js] +skip-if = e10s && debug +[browser_dbg_variables-view-filter-pref.js] +skip-if = e10s && debug +[browser_dbg_variables-view-filter-searchbox.js] +skip-if = e10s && debug +[browser_dbg_variables-view-frame-parameters-01.js] +skip-if = e10s && debug +[browser_dbg_variables-view-frame-parameters-02.js] +skip-if = e10s && debug +[browser_dbg_variables-view-frame-parameters-03.js] +skip-if = e10s && debug +[browser_dbg_variables-view-frame-with.js] +skip-if = e10s && debug +[browser_dbg_variables-view-frozen-sealed-nonext.js] +skip-if = e10s && debug +[browser_dbg_variables-view-hide-non-enums.js] +[browser_dbg_variables-view-large-array-buffer.js] +[browser_dbg_variables-view-map-set.js] +skip-if = e10s && debug +[browser_dbg_variables-view-override-01.js] +skip-if = e10s && debug +[browser_dbg_variables-view-override-02.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-01.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-02.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-03.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-04.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-05.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-06.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-07.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-08.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-09.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-10.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-11.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-12.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-13.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-14.js] +skip-if = true # Bug 1029545 +[browser_dbg_variables-view-popup-15.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-16.js] +skip-if = e10s && debug +[browser_dbg_variables-view-popup-17.js] +skip-if = e10s && debug +[browser_dbg_variables-view-reexpand-01.js] +skip-if = e10s && debug +[browser_dbg_variables-view-reexpand-02.js] +skip-if = e10s && debug +[browser_dbg_variables-view-reexpand-03.js] +skip-if = e10s && debug +[browser_dbg_variables-view-webidl.js] +skip-if = e10s && debug +[browser_dbg_watch-expressions-01.js] +skip-if = e10s && debug +[browser_dbg_watch-expressions-02.js] +skip-if = e10s && debug +[browser_dbg_worker-console-01.js] +skip-if = e10s && debug +[browser_dbg_worker-console-02.js] +skip-if = e10s && debug +[browser_dbg_worker-console-03.js] +skip-if = e10s && debug +[browser_dbg_worker-source-map.js] +skip-if = e10s && debug +[browser_dbg_worker-window.js] +skip-if = e10s && debug +[browser_dbg_WorkerActor.attach.js] +skip-if = e10s && debug +[browser_dbg_WorkerActor.attachThread.js] +skip-if = e10s && debug +[browser_dbg_split-console-keypress.js] +skip-if = e10s && (debug || os == "linux") # Bug 1214439 diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_WorkerActor.attach.js b/devtools/client/debugger/test/mochitest/browser_dbg_WorkerActor.attach.js new file mode 100644 index 000000000..68d7f1b26 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_WorkerActor.attach.js @@ -0,0 +1,62 @@ +var MAX_TOTAL_VIEWERS = "browser.sessionhistory.max_total_viewers"; + +var TAB1_URL = EXAMPLE_URL + "doc_WorkerActor.attach-tab1.html"; +var TAB2_URL = EXAMPLE_URL + "doc_WorkerActor.attach-tab2.html"; +var WORKER1_URL = "code_WorkerActor.attach-worker1.js"; +var WORKER2_URL = "code_WorkerActor.attach-worker2.js"; + +function test() { + Task.spawn(function* () { + let oldMaxTotalViewers = SpecialPowers.getIntPref(MAX_TOTAL_VIEWERS); + SpecialPowers.setIntPref(MAX_TOTAL_VIEWERS, 10); + + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let tab = yield addTab(TAB1_URL); + let { tabs } = yield listTabs(client); + let [, tabClient] = yield attachTab(client, findTab(tabs, TAB1_URL)); + yield listWorkers(tabClient); + + // If a page still has pending network requests, it will not be moved into + // the bfcache. Consequently, we cannot use waitForWorkerListChanged here, + // because the worker is not guaranteed to have finished loading when it is + // registered. Instead, we have to wait for the promise returned by + // createWorker in the tab to be resolved. + yield createWorkerInTab(tab, WORKER1_URL); + let { workers } = yield listWorkers(tabClient); + let [, workerClient1] = yield attachWorker(tabClient, + findWorker(workers, WORKER1_URL)); + is(workerClient1.isClosed, false, "worker in tab 1 should not be closed"); + + executeSoon(() => { + tab.linkedBrowser.loadURI(TAB2_URL); + }); + yield waitForWorkerClose(workerClient1); + is(workerClient1.isClosed, true, "worker in tab 1 should be closed"); + + yield createWorkerInTab(tab, WORKER2_URL); + ({ workers } = yield listWorkers(tabClient)); + let [, workerClient2] = yield attachWorker(tabClient, + findWorker(workers, WORKER2_URL)); + is(workerClient2.isClosed, false, "worker in tab 2 should not be closed"); + + executeSoon(() => { + tab.linkedBrowser.contentWindow.history.back(); + }); + yield waitForWorkerClose(workerClient2); + is(workerClient2.isClosed, true, "worker in tab 2 should be closed"); + + ({ workers } = yield listWorkers(tabClient)); + [, workerClient1] = yield attachWorker(tabClient, + findWorker(workers, WORKER1_URL)); + is(workerClient1.isClosed, false, "worker in tab 1 should not be closed"); + + yield close(client); + SpecialPowers.setIntPref(MAX_TOTAL_VIEWERS, oldMaxTotalViewers); + finish(); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_WorkerActor.attachThread.js b/devtools/client/debugger/test/mochitest/browser_dbg_WorkerActor.attachThread.js new file mode 100644 index 000000000..34e59a418 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_WorkerActor.attachThread.js @@ -0,0 +1,100 @@ +var TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html"; +var WORKER_URL = "code_WorkerActor.attachThread-worker.js"; + +function test() { + Task.spawn(function* () { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + let client1 = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client1); + let client2 = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client2); + + let tab = yield addTab(TAB_URL); + let { tabs: tabs1 } = yield listTabs(client1); + let [, tabClient1] = yield attachTab(client1, findTab(tabs1, TAB_URL)); + let { tabs: tabs2 } = yield listTabs(client2); + let [, tabClient2] = yield attachTab(client2, findTab(tabs2, TAB_URL)); + + yield listWorkers(tabClient1); + yield listWorkers(tabClient2); + yield createWorkerInTab(tab, WORKER_URL); + let { workers: workers1 } = yield listWorkers(tabClient1); + let [, workerClient1] = yield attachWorker(tabClient1, + findWorker(workers1, WORKER_URL)); + let { workers: workers2 } = yield listWorkers(tabClient2); + let [, workerClient2] = yield attachWorker(tabClient2, + findWorker(workers2, WORKER_URL)); + + let location = { line: 5 }; + + let [, threadClient1] = yield attachThread(workerClient1); + let sources1 = yield getSources(threadClient1); + let sourceClient1 = threadClient1.source(findSource(sources1, + EXAMPLE_URL + WORKER_URL)); + let [, breakpointClient1] = yield setBreakpoint(sourceClient1, location); + yield resume(threadClient1); + + let [, threadClient2] = yield attachThread(workerClient2); + let sources2 = yield getSources(threadClient2); + let sourceClient2 = threadClient2.source(findSource(sources2, + EXAMPLE_URL + WORKER_URL)); + let [, breakpointClient2] = yield setBreakpoint(sourceClient2, location); + yield resume(threadClient2); + + let packet = yield source(sourceClient1); + let text = (yield new Promise(function (resolve) { + let request = new XMLHttpRequest(); + request.open("GET", EXAMPLE_URL + WORKER_URL, true); + request.send(); + request.onload = function () { + resolve(request.responseText); + }; + })); + is(packet.source, text); + + postMessageToWorkerInTab(tab, WORKER_URL, "ping"); + yield Promise.all([ + waitForPause(threadClient1).then((packet) => { + is(packet.type, "paused"); + let why = packet.why; + is(why.type, "breakpoint"); + is(why.actors.length, 1); + is(why.actors[0], breakpointClient1.actor); + let frame = packet.frame; + let where = frame.where; + is(where.source.actor, sourceClient1.actor); + is(where.line, location.line); + let variables = frame.environment.bindings.variables; + is(variables.a.value, 1); + is(variables.b.value.type, "undefined"); + is(variables.c.value.type, "undefined"); + return resume(threadClient1); + }), + waitForPause(threadClient2).then((packet) => { + is(packet.type, "paused"); + let why = packet.why; + is(why.type, "breakpoint"); + is(why.actors.length, 1); + is(why.actors[0], breakpointClient2.actor); + let frame = packet.frame; + let where = frame.where; + is(where.source.actor, sourceClient2.actor); + is(where.line, location.line); + let variables = frame.environment.bindings.variables; + is(variables.a.value, 1); + is(variables.b.value.type, "undefined"); + is(variables.c.value.type, "undefined"); + return resume(threadClient2); + }), + ]); + + terminateWorkerInTab(tab, WORKER_URL); + yield waitForWorkerClose(workerClient1); + yield waitForWorkerClose(workerClient2); + yield close(client1); + yield close(client2); + finish(); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_aaa_run_first_leaktest.js b/devtools/client/debugger/test/mochitest/browser_dbg_aaa_run_first_leaktest.js new file mode 100644 index 000000000..bd612456b --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_aaa_run_first_leaktest.js @@ -0,0 +1,33 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This tests if the debugger leaks on initialization and sudden destruction. + * You can also use this initialization format as a template for other tests. + * If leaks happen here, there's something very, very fishy going on. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + // Wait longer for this very simple test that comes first, to make sure that + // GC from previous tests does not interfere with the debugger suite. + requestLongerTimeout(2); + + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + ok(aTab, "Should have a tab available."); + ok(aPanel, "Should have a debugger pane available."); + + waitForSourceAndCaretAndScopes(aPanel, "-02.js", 1).then(() => { + resumeDebuggerThenCloseAndFinish(aPanel); + }); + + callInTab(aTab, "firstCall"); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_addon-console.js b/devtools/client/debugger/test/mochitest/browser_dbg_addon-console.js new file mode 100644 index 000000000..cf615f181 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_addon-console.js @@ -0,0 +1,47 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that the we can see console messages from the add-on + +const ADDON_ID = "browser_dbg_addon4@tests.mozilla.org"; +const ADDON_PATH = "addon4.xpi"; + +function getCachedMessages(webConsole) { + let deferred = promise.defer(); + webConsole.getCachedMessages(["ConsoleAPI"], (aResponse) => { + if (aResponse.error) { + deferred.reject(aResponse.error); + return; + } + deferred.resolve(aResponse.messages); + }); + return deferred.promise; +} + +function test() { + Task.spawn(function* () { + let addon = yield addTemporaryAddon(ADDON_PATH); + let addonDebugger = yield initAddonDebugger(ADDON_ID); + + let webConsole = addonDebugger.webConsole; + let messages = yield getCachedMessages(webConsole); + is(messages.length, 1, "Should be one cached message"); + is(messages[0].arguments[0].type, "object", "Should have logged an object"); + is(messages[0].arguments[0].preview.ownProperties.msg.value, "Hello from the test add-on", "Should have got the right message"); + + let consolePromise = addonDebugger.once("console"); + + console.log("Bad message"); + Services.obs.notifyObservers(null, "addon-test-ping", ""); + + let messageGrip = yield consolePromise; + is(messageGrip.arguments[0].type, "object", "Should have logged an object"); + is(messageGrip.arguments[0].preview.ownProperties.msg.value, "Hello again", "Should have got the right message"); + + yield addonDebugger.destroy(); + yield removeAddon(addon); + finish(); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_addon-modules-unpacked.js b/devtools/client/debugger/test/mochitest/browser_dbg_addon-modules-unpacked.js new file mode 100644 index 000000000..5784eecb4 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_addon-modules-unpacked.js @@ -0,0 +1,67 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Make sure the add-on actor can see loaded JS Modules from an add-on + +const ADDON_ID = "browser_dbg_addon5@tests.mozilla.org"; +const ADDON_PATH = "addon-source/browser_dbg_addon5/"; +const ADDON_URL = getTemporaryAddonURLFromPath(ADDON_PATH); + +function test() { + Task.spawn(function* () { + let addon = yield addTemporaryAddon(ADDON_PATH); + let tab1 = yield addTab("chrome://browser_dbg_addon5/content/test.xul"); + + let addonDebugger = yield initAddonDebugger(ADDON_ID); + + is(addonDebugger.title, + `Developer Tools - Test unpacked add-on with JS Modules - ${ADDON_URL}`, + "Saw the right toolbox title."); + + // Check the inital list of sources is correct + let groups = yield addonDebugger.getSourceGroups(); + is(groups[0].name, "browser_dbg_addon5@tests.mozilla.org", "Add-on code should be the first group"); + is(groups[1].name, "chrome://global", "XUL code should be the second group"); + is(groups.length, 2, "Should be only two groups."); + + let sources = groups[0].sources; + is(sources.length, 3, "Should be three sources"); + ok(sources[0].url.endsWith("/browser_dbg_addon5/bootstrap.js"), "correct url for bootstrap code"); + is(sources[0].label, "bootstrap.js", "correct label for bootstrap code"); + is(sources[1].url, "resource://browser_dbg_addon5/test.jsm", "correct url for addon code"); + is(sources[1].label, "test.jsm", "correct label for addon code"); + is(sources[2].url, "chrome://browser_dbg_addon5/content/testxul.js", "correct url for addon tab code"); + is(sources[2].label, "testxul.js", "correct label for addon tab code"); + + // Load a new module and tab and check they appear in the list of sources + Cu.import("resource://browser_dbg_addon5/test2.jsm", {}); + let tab2 = yield addTab("chrome://browser_dbg_addon5/content/test2.xul"); + + groups = yield addonDebugger.getSourceGroups(); + is(groups[0].name, "browser_dbg_addon5@tests.mozilla.org", "Add-on code should be the first group"); + is(groups[1].name, "chrome://global", "XUL code should be the second group"); + is(groups.length, 2, "Should be only two groups."); + + sources = groups[0].sources; + is(sources.length, 5, "Should be five sources"); + ok(sources[0].url.endsWith("/browser_dbg_addon5/bootstrap.js"), "correct url for bootstrap code"); + is(sources[0].label, "bootstrap.js", "correct label for bootstrap code"); + is(sources[1].url, "resource://browser_dbg_addon5/test.jsm", "correct url for addon code"); + is(sources[1].label, "test.jsm", "correct label for addon code"); + is(sources[2].url, "chrome://browser_dbg_addon5/content/testxul.js", "correct url for addon tab code"); + is(sources[2].label, "testxul.js", "correct label for addon tab code"); + is(sources[3].url, "resource://browser_dbg_addon5/test2.jsm", "correct url for addon code"); + is(sources[3].label, "test2.jsm", "correct label for addon code"); + is(sources[4].url, "chrome://browser_dbg_addon5/content/testxul2.js", "correct url for addon tab code"); + is(sources[4].label, "testxul2.js", "correct label for addon tab code"); + + Cu.unload("resource://browser_dbg_addon5/test2.jsm"); + yield addonDebugger.destroy(); + yield removeTab(tab1); + yield removeTab(tab2); + yield removeAddon(addon); + finish(); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_addon-modules.js b/devtools/client/debugger/test/mochitest/browser_dbg_addon-modules.js new file mode 100644 index 000000000..1ff7c600d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_addon-modules.js @@ -0,0 +1,66 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Make sure the add-on actor can see loaded JS Modules from an add-on + +const ADDON_ID = "browser_dbg_addon4@tests.mozilla.org"; +const ADDON_PATH = "addon4.xpi"; +const ADDON_URL = getTemporaryAddonURLFromPath(ADDON_PATH); + +function test() { + Task.spawn(function* () { + let addon = yield addTemporaryAddon(ADDON_PATH); + let tab1 = yield addTab("chrome://browser_dbg_addon4/content/test.xul"); + + let addonDebugger = yield initAddonDebugger(ADDON_ID); + + is(addonDebugger.title, `Developer Tools - Test add-on with JS Modules - ${ADDON_URL}`, + "Saw the right toolbox title."); + + // Check the inital list of sources is correct + let groups = yield addonDebugger.getSourceGroups(); + is(groups[0].name, "browser_dbg_addon4@tests.mozilla.org", "Add-on code should be the first group"); + is(groups[1].name, "chrome://global", "XUL code should be the second group"); + is(groups.length, 2, "Should be only two groups."); + + let sources = groups[0].sources; + is(sources.length, 3, "Should be three sources"); + ok(sources[0].url.endsWith("/addon4.xpi!/bootstrap.js"), "correct url for bootstrap code"); + is(sources[0].label, "bootstrap.js", "correct label for bootstrap code"); + is(sources[1].url, "resource://browser_dbg_addon4/test.jsm", "correct url for addon code"); + is(sources[1].label, "test.jsm", "correct label for addon code"); + is(sources[2].url, "chrome://browser_dbg_addon4/content/testxul.js", "correct url for addon tab code"); + is(sources[2].label, "testxul.js", "correct label for addon tab code"); + + // Load a new module and tab and check they appear in the list of sources + Cu.import("resource://browser_dbg_addon4/test2.jsm", {}); + let tab2 = yield addTab("chrome://browser_dbg_addon4/content/test2.xul"); + + groups = yield addonDebugger.getSourceGroups(); + is(groups[0].name, "browser_dbg_addon4@tests.mozilla.org", "Add-on code should be the first group"); + is(groups[1].name, "chrome://global", "XUL code should be the second group"); + is(groups.length, 2, "Should be only two groups."); + + sources = groups[0].sources; + is(sources.length, 5, "Should be five sources"); + ok(sources[0].url.endsWith("/addon4.xpi!/bootstrap.js"), "correct url for bootstrap code"); + is(sources[0].label, "bootstrap.js", "correct label for bootstrap code"); + is(sources[1].url, "resource://browser_dbg_addon4/test.jsm", "correct url for addon code"); + is(sources[1].label, "test.jsm", "correct label for addon code"); + is(sources[2].url, "chrome://browser_dbg_addon4/content/testxul.js", "correct url for addon tab code"); + is(sources[2].label, "testxul.js", "correct label for addon tab code"); + is(sources[3].url, "resource://browser_dbg_addon4/test2.jsm", "correct url for addon code"); + is(sources[3].label, "test2.jsm", "correct label for addon code"); + is(sources[4].url, "chrome://browser_dbg_addon4/content/testxul2.js", "correct url for addon tab code"); + is(sources[4].label, "testxul2.js", "correct label for addon tab code"); + + Cu.unload("resource://browser_dbg_addon4/test2.jsm"); + yield addonDebugger.destroy(); + yield removeTab(tab1); + yield removeTab(tab2); + yield removeAddon(addon); + finish(); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_addon-panels.js b/devtools/client/debugger/test/mochitest/browser_dbg_addon-panels.js new file mode 100644 index 000000000..aeda501b0 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_addon-panels.js @@ -0,0 +1,49 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Ensure that only panels that are relevant to the addon debugger +// display in the toolbox + +const ADDON_ID = "jid1-ami3akps3baaeg@jetpack"; +const ADDON_PATH = "addon3.xpi"; + +var gAddon, gClient, gThreadClient, gDebugger, gSources; +var PREFS = [ + ["devtools.canvasdebugger.enabled", true], + ["devtools.shadereditor.enabled", true], + ["devtools.performance.enabled", true], + ["devtools.netmonitor.enabled", true], + ["devtools.scratchpad.enabled", true] +]; +function test() { + Task.spawn(function* () { + // Store and enable all optional dev tools panels + yield pushPrefs(...PREFS); + + let addon = yield addTemporaryAddon(ADDON_PATH); + let addonDebugger = yield initAddonDebugger(ADDON_ID); + + // Check only valid tabs are shown + let tabs = addonDebugger.frame.contentDocument.getElementById("toolbox-tabs").children; + let expectedTabs = ["webconsole", "jsdebugger", "scratchpad"]; + + is(tabs.length, expectedTabs.length, "displaying only " + expectedTabs.length + " tabs in addon debugger"); + Array.forEach(tabs, (tab, i) => { + let toolName = expectedTabs[i]; + is(tab.getAttribute("toolid"), toolName, "displaying " + toolName); + }); + + // Check no toolbox buttons are shown + let buttons = addonDebugger.frame.contentDocument.getElementById("toolbox-buttons").children; + Array.forEach(buttons, (btn, i) => { + is(btn.hidden, true, "no toolbox buttons for the addon debugger -- " + btn.className); + }); + + yield addonDebugger.destroy(); + yield removeAddon(addon); + + finish(); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_addon-sources.js b/devtools/client/debugger/test/mochitest/browser_dbg_addon-sources.js new file mode 100644 index 000000000..eaa4741eb --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_addon-sources.js @@ -0,0 +1,42 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Ensure that the sources listed when debugging an addon are either from the +// addon itself, or the SDK, with proper groups and labels. + +const ADDON_ID = "jid1-ami3akps3baaeg@jetpack"; +const ADDON_PATH = "addon3.xpi"; +const ADDON_URL = getTemporaryAddonURLFromPath(ADDON_PATH); + +var gClient; + +function test() { + Task.spawn(function* () { + let addon = yield addTemporaryAddon(ADDON_PATH); + let addonDebugger = yield initAddonDebugger(ADDON_ID); + + is(addonDebugger.title, `Developer Tools - browser_dbg_addon3 - ${ADDON_URL}`, + "Saw the right toolbox title."); + + // Check the inital list of sources is correct + let groups = yield addonDebugger.getSourceGroups(); + is(groups[0].name, "jid1-ami3akps3baaeg@jetpack", "Add-on code should be the first group"); + is(groups[1].name, "Add-on SDK", "Add-on SDK should be the second group"); + is(groups.length, 2, "Should be only two groups."); + + let sources = groups[0].sources; + is(sources.length, 2, "Should be two sources"); + ok(sources[0].url.endsWith("/addon3.xpi!/bootstrap.js"), "correct url for bootstrap code"); + is(sources[0].label, "bootstrap.js", "correct label for bootstrap code"); + is(sources[1].url, "resource://jid1-ami3akps3baaeg-at-jetpack/browser_dbg_addon3/lib/main.js", "correct url for add-on code"); + is(sources[1].label, "resources/browser_dbg_addon3/lib/main.js", "correct label for add-on code"); + + ok(groups[1].sources.length > 10, "SDK modules are listed"); + + yield addonDebugger.destroy(); + yield removeAddon(addon); + finish(); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_addon-workers-dbg-enabled.js b/devtools/client/debugger/test/mochitest/browser_dbg_addon-workers-dbg-enabled.js new file mode 100644 index 000000000..158a3d69e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_addon-workers-dbg-enabled.js @@ -0,0 +1,41 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the Addon Debugger works when devtools.debugger.workers is enabled. +// Workers controller cannot be used when debugging an Addon actor. + +const ADDON_ID = "jid1-ami3akps3baaeg@jetpack"; +const ADDON_PATH = "addon3.xpi"; +const ADDON_URL = getTemporaryAddonURLFromPath(ADDON_PATH); + +function test() { + Task.spawn(function* () { + info("Enable worker debugging."); + yield new Promise(resolve => { + SpecialPowers.pushPrefEnv({ + "set": [["devtools.debugger.workers", true]] + }, resolve); + }); + + let addon = yield addTemporaryAddon(ADDON_PATH); + let addonDebugger = yield initAddonDebugger(ADDON_ID); + + is(addonDebugger.title, + `Developer Tools - browser_dbg_addon3 - ${ADDON_URL}`, + "Saw the right toolbox title."); + + info("Check that groups and sources are displayed."); + let groups = yield addonDebugger.getSourceGroups(); + is(groups.length, 2, "Should be only two groups."); + let sources = groups[0].sources; + is(sources.length, 2, "Should be two sources"); + + yield addonDebugger.destroy(); + yield removeAddon(addon); + finish(); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_addonactor.js b/devtools/client/debugger/test/mochitest/browser_dbg_addonactor.js new file mode 100644 index 000000000..1bee0b933 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_addonactor.js @@ -0,0 +1,95 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Make sure we can attach to addon actors. + +const ADDON3_PATH = "addon3.xpi"; +const ADDON3_ID = "jid1-ami3akps3baaeg@jetpack"; +const ADDON_MODULE_URL = "resource://jid1-ami3akps3baaeg-at-jetpack/browser_dbg_addon3/lib/main.js"; + +var gAddon, gClient, gThreadClient; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + installAddon() + .then(attachAddonActorForId.bind(null, gClient, ADDON3_ID)) + .then(attachAddonThread) + .then(testDebugger) + .then(testSources) + .then(() => gClient.close()) + .then(uninstallAddon) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function installAddon() { + return addTemporaryAddon(ADDON3_PATH).then(aAddon => { + gAddon = aAddon; + }); +} + +function attachAddonThread([aGrip, aResponse]) { + info("attached addon actor for Addon ID"); + let deferred = promise.defer(); + + gClient.attachThread(aResponse.threadActor, (aResponse, aThreadClient) => { + info("attached thread"); + gThreadClient = aThreadClient; + gThreadClient.resume(deferred.resolve); + }); + return deferred.promise; +} + +function testDebugger() { + info("Entering testDebugger"); + let deferred = promise.defer(); + + once(gClient, "paused").then(() => { + ok(true, "Should be able to attach to addon actor"); + gThreadClient.resume(deferred.resolve); + }); + + Services.obs.notifyObservers(null, "debuggerAttached", null); + + return deferred.promise; +} + +function testSources() { + let deferred = promise.defer(); + + gThreadClient.getSources(aResponse => { + // source URLs contain launch-specific temporary directory path, + // hence the ".contains" call. + const matches = aResponse.sources.filter(s => s.url.includes(ADDON_MODULE_URL)); + ok(matches.length > 0, + "the main script of the addon is present in the source list"); + deferred.resolve(); + }); + + return deferred.promise; +} + +function uninstallAddon() { + return removeAddon(gAddon); +} + +registerCleanupFunction(function () { + gClient = null; + gAddon = null; + gThreadClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-01.js new file mode 100644 index 000000000..dcad48cf8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-01.js @@ -0,0 +1,117 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test auto pretty printing. + +const TAB_URL = EXAMPLE_URL + "doc_auto-pretty-print-01.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources, gPrefs, gOptions, gView; + +var gFirstSource = EXAMPLE_URL + "code_ugly-5.js"; +var gSecondSource = EXAMPLE_URL + "code_ugly-6.js"; + +var gOriginalPref = Services.prefs.getBoolPref("devtools.debugger.auto-pretty-print"); + +function test() { + let options = { + source: gFirstSource, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gPrefs = gDebugger.Prefs; + gOptions = gDebugger.DebuggerView.Options; + gView = gDebugger.DebuggerView; + + Task.spawn(function* () { + testSourceIsUgly(); + + enableAutoPrettyPrint(); + testAutoPrettyPrintOn(); + + reload(gPanel); + yield waitForSourceShown(gPanel, gFirstSource); + testSourceIsUgly(); + yield waitForSourceShown(gPanel, gFirstSource); + testSourceIsPretty(); + disableAutoPrettyPrint(); + testAutoPrettyPrintOff(); + + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN); + gSources.selectedIndex = 1; + yield finished; + + testSecondSourceLabel(); + testSourceIsUgly(); + + enableAutoPrettyPrint(); + yield closeDebuggerAndFinish(gPanel); + }); + }); +} + +function testSourceIsUgly() { + ok(!gEditor.getText().includes("\n "), + "The source shouldn't be pretty printed yet."); +} + +function testSecondSourceLabel() { + let source = gSources.selectedItem.attachment.source; + ok(source.url === gSecondSource, + "Second source url is correct."); +} + +function testProgressBarShown() { + const deck = gDebugger.document.getElementById("editor-deck"); + is(deck.selectedIndex, 2, "The progress bar should be shown"); +} + +function testAutoPrettyPrintOn() { + is(gPrefs.autoPrettyPrint, true, + "The auto-pretty-print pref should be on."); + is(gOptions._autoPrettyPrint.getAttribute("checked"), "true", + "The Auto pretty print menu item should be checked."); +} + +function disableAutoPrettyPrint() { + gOptions._autoPrettyPrint.setAttribute("checked", "false"); + gOptions._toggleAutoPrettyPrint(); + gOptions._onPopupHidden(); +} + +function enableAutoPrettyPrint() { + gOptions._autoPrettyPrint.setAttribute("checked", "true"); + gOptions._toggleAutoPrettyPrint(); + gOptions._onPopupHidden(); +} + +function testAutoPrettyPrintOff() { + is(gPrefs.autoPrettyPrint, false, + "The auto-pretty-print pref should be off."); + isnot(gOptions._autoPrettyPrint.getAttribute("checked"), "true", + "The Auto pretty print menu item should not be checked."); +} + +function testSourceIsPretty() { + ok(gEditor.getText().includes("\n "), + "The source should be pretty printed."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; + gOptions = null; + gPrefs = null; + gView = null; + Services.prefs.setBoolPref("devtools.debugger.auto-pretty-print", gOriginalPref); +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-02.js new file mode 100644 index 000000000..432bc73d2 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-02.js @@ -0,0 +1,126 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that auto pretty printing doesn't accidentally toggle + * pretty printing off when we switch to a minified source + * that is already pretty printed. + */ + +const TAB_URL = EXAMPLE_URL + "doc_auto-pretty-print-02.html"; + +var gTab, gDebuggee, gPanel, gDebugger; +var gEditor, gSources, gPrefs, gOptions, gView; + +var gFirstSource = EXAMPLE_URL + "code_ugly-6.js"; +var gSecondSource = EXAMPLE_URL + "code_ugly-7.js"; + +var gOriginalPref = Services.prefs.getBoolPref("devtools.debugger.auto-pretty-print"); +Services.prefs.setBoolPref("devtools.debugger.auto-pretty-print", true); + +function test() { + let options = { + source: gFirstSource, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => { + const gTab = aTab; + const gDebuggee = aDebuggee; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const gPrefs = gDebugger.Prefs; + const gOptions = gDebugger.DebuggerView.Options; + const gView = gDebugger.DebuggerView; + + // Should be on by default. + testAutoPrettyPrintOn(); + + Task.spawn(function* () { + + testSourceIsUgly(); + + yield waitForSourceShown(gPanel, gFirstSource); + testSourceIsPretty(); + testPrettyPrintButtonOn(); + + // select second source + yield selectSecondSource(); + testSecondSourceLabel(); + + // select first source + yield selectFirstSource(); + testFirstSourceLabel(); + testPrettyPrintButtonOn(); + + // Disable auto pretty printing so it does not affect the following tests. + yield disableAutoPrettyPrint(); + + closeDebuggerAndFinish(gPanel) + .then(null, aError => { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError)); + }); + }); + + function selectSecondSource() { + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN, 2); + gSources.selectedIndex = 1; + return finished; + } + + function selectFirstSource() { + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN); + gSources.selectedIndex = 0; + return finished; + } + + function testSourceIsUgly() { + ok(!gEditor.getText().includes("\n "), + "The source shouldn't be pretty printed yet."); + } + + function testFirstSourceLabel() { + let source = gSources.selectedItem.attachment.source; + ok(source.url === gFirstSource, + "First source url is correct."); + } + + function testSecondSourceLabel() { + let source = gSources.selectedItem.attachment.source; + ok(source.url === gSecondSource, + "Second source url is correct."); + } + + function testAutoPrettyPrintOn() { + is(gPrefs.autoPrettyPrint, true, + "The auto-pretty-print pref should be on."); + is(gOptions._autoPrettyPrint.getAttribute("checked"), "true", + "The Auto pretty print menu item should be checked."); + } + + function testPrettyPrintButtonOn() { + is(gDebugger.document.getElementById("pretty-print").checked, true, + "The button should be checked when the source is selected."); + } + + function disableAutoPrettyPrint() { + gOptions._autoPrettyPrint.setAttribute("checked", "false"); + gOptions._toggleAutoPrettyPrint(); + gOptions._onPopupHidden(); + info("Disabled auto pretty printing."); + } + + function testSourceIsPretty() { + ok(gEditor.getText().includes("\n "), + "The source should be pretty printed."); + } + + registerCleanupFunction(function () { + Services.prefs.setBoolPref("devtools.debugger.auto-pretty-print", gOriginalPref); + }); + + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-03.js new file mode 100644 index 000000000..bf73ef0cc --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-03.js @@ -0,0 +1,58 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * If auto pretty-printing it enabled, make sure that if + * pretty-printing fails that it still properly shows the original + * source. + */ + +const TAB_URL = EXAMPLE_URL + "doc_auto-pretty-print-02.html"; + +var FIRST_SOURCE = EXAMPLE_URL + "code_ugly-6.js"; +var SECOND_SOURCE = EXAMPLE_URL + "code_ugly-7.js"; + +function test() { + let options = { + source: FIRST_SOURCE, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + + const gController = gDebugger.DebuggerController; + const gEditor = gDebugger.DebuggerView.editor; + const constants = gDebugger.require("./content/constants"); + const queries = gDebugger.require("./content/queries"); + const actions = bindActionCreators(gPanel); + + Task.spawn(function* () { + const secondSource = queries.getSourceByURL(gController.getState(), SECOND_SOURCE); + actions.selectSource(secondSource); + + // It should be showing the loading text + is(gEditor.getText(), gDebugger.DebuggerView._loadingText, + "The editor loading text is shown"); + + gController.dispatch({ + type: constants.TOGGLE_PRETTY_PRINT, + status: "error", + source: secondSource, + }); + + is(gEditor.getText(), gDebugger.DebuggerView._loadingText, + "The editor loading text is shown"); + + yield waitForSourceShown(gPanel, SECOND_SOURCE); + + ok(gEditor.getText().includes("function foo"), + "The second source is shown"); + + yield closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_bfcache.js b/devtools/client/debugger/test/mochitest/browser_dbg_bfcache.js new file mode 100644 index 000000000..8b3c9ca34 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_bfcache.js @@ -0,0 +1,95 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the debugger is updated with the correct sources when moving + * back and forward in the tab. + */ + +const TAB_URL_1 = EXAMPLE_URL + "doc_script-switching-01.html"; +const TAB_URL_2 = EXAMPLE_URL + "doc_recursion-stack.html"; + +var gTab, gDebuggee, gPanel, gDebugger; +var gSources; + +const test = Task.async(function* () { + info("Starting browser_dbg_bfcache.js's `test`."); + + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + ([gTab, gDebuggee, gPanel]) = yield initDebugger(TAB_URL_1, options); + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + + yield testFirstPage(); + yield testLocationChange(); + yield testBack(); + yield testForward(); + return closeDebuggerAndFinish(gPanel); +}); + +function testFirstPage() { + info("Testing first page."); + + // Spin the event loop before causing the debuggee to pause, to allow + // this function to return first. + executeSoon(() => gDebuggee.firstCall()); + + return waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1) + .then(validateFirstPage); +} + +function testLocationChange() { + info("Navigating to a different page."); + + return navigateActiveTabTo(gPanel, + TAB_URL_2, + gDebugger.EVENTS.SOURCES_ADDED) + .then(validateSecondPage); +} + +function testBack() { + info("Going back."); + + return navigateActiveTabInHistory(gPanel, + "back", + gDebugger.EVENTS.SOURCES_ADDED) + .then(validateFirstPage); +} + +function testForward() { + info("Going forward."); + + return navigateActiveTabInHistory(gPanel, + "forward", + gDebugger.EVENTS.SOURCES_ADDED) + .then(validateSecondPage); +} + +function validateFirstPage() { + is(gSources.itemCount, 2, + "Found the expected number of sources."); + ok(gSources.getItemForAttachment(e => e.label == "code_script-switching-01.js"), + "Found the first source label."); + ok(gSources.getItemForAttachment(e => e.label == "code_script-switching-02.js"), + "Found the second source label."); +} + +function validateSecondPage() { + is(gSources.itemCount, 1, + "Found the expected number of sources."); + ok(gSources.getItemForAttachment(e => e.label == "doc_recursion-stack.html"), + "Found the single source label."); +} + +registerCleanupFunction(function () { + gTab = null; + gDebuggee = null; + gPanel = null; + gDebugger = null; + gSources = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-01.js new file mode 100644 index 000000000..f4ecd5b95 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-01.js @@ -0,0 +1,57 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that if we black box a source and then refresh, it is still black boxed. + */ + +const TAB_URL = EXAMPLE_URL + "doc_binary_search.html"; + +var gTab, gPanel, gDebugger; + +function test() { + let options = { + source: EXAMPLE_URL + "code_binary_search.coffee", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + + testBlackBoxSource() + .then(testBlackBoxReload) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testBlackBoxSource() { + const bbButton = getBlackBoxButton(gPanel); + ok(!bbButton.checked, "Should not be black boxed by default"); + + return toggleBlackBoxing(gPanel).then(source => { + ok(source.isBlackBoxed, "The source should be black boxed now."); + ok(bbButton.checked, "The checkbox should no longer be checked."); + }); +} + +function testBlackBoxReload() { + return reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(() => { + const bbButton = getBlackBoxButton(gPanel); + const selectedSource = getSelectedSourceElement(gPanel); + ok(bbButton.checked, "Should still be black boxed."); + ok(selectedSource.classList.contains("black-boxed"), + "'black-boxed' class should still be applied"); + }); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-02.js new file mode 100644 index 000000000..2eca3ec92 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-02.js @@ -0,0 +1,60 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that black boxed frames are compressed into a single frame on the stack + * view. + */ + +const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html"; +const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js"; + +var gTab, gPanel, gDebugger; +var gFrames; + +function test() { + let options = { + source: BLACKBOXME_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gFrames = gDebugger.DebuggerView.StackFrames; + + testBlackBoxSource() + .then(testBlackBoxStack) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testBlackBoxSource() { + return toggleBlackBoxing(gPanel).then(source => { + ok(source.isBlackBoxed, "The source should be black boxed now."); + }); +} + +function testBlackBoxStack() { + let finished = waitForSourceAndCaretAndScopes(gPanel, ".html", 21).then(() => { + is(gFrames.itemCount, 3, + "Should only get 3 frames."); + is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 1, + "And one of them should be the combined black boxed frames."); + }); + + callInTab(gTab, "runTest"); + return finished; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gFrames = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-03.js new file mode 100644 index 000000000..fa4489ac7 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-03.js @@ -0,0 +1,65 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that black boxed frames are compressed into a single frame on the stack + * view when we are already paused. + */ + +const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html"; +const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js"; + +var gTab, gPanel, gDebugger; +var gFrames, gSources; + +function test() { + let options = { + source: BLACKBOXME_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gFrames = gDebugger.DebuggerView.StackFrames; + gSources = gDebugger.DebuggerView.Sources; + + waitForSourceAndCaretAndScopes(gPanel, ".html", 21) + .then(testBlackBoxStack) + .then(testBlackBoxSource) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + callInTab(gTab, "runTest"); + }); +} + +function testBlackBoxStack() { + is(gFrames.itemCount, 6, + "Should get 6 frames."); + is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 0, + "And none of them are black boxed."); +} + +function testBlackBoxSource() { + return toggleBlackBoxing(gPanel, getSourceActor(gSources, BLACKBOXME_URL)).then(aSource => { + ok(aSource.isBlackBoxed, "The source should be black boxed now."); + + is(gFrames.itemCount, 3, + "Should only get 3 frames."); + is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 1, + "And one of them should be the combined black boxed frames."); + }); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gFrames = null; + gSources = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-04.js new file mode 100644 index 000000000..ea9cd84f3 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-04.js @@ -0,0 +1,65 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we get a stack frame for each black boxed source, not a single one + * for all of them. + */ + +const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html"; +const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js"; + +var gTab, gPanel, gDebugger; +var gFrames, gSources; + +function test() { + let options = { + source: BLACKBOXME_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gFrames = gDebugger.DebuggerView.StackFrames; + gSources = gDebugger.DebuggerView.Sources; + + blackBoxSources() + .then(testBlackBoxStack) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function blackBoxSources() { + let finished = waitForThreadEvents(gPanel, "blackboxchange", 3); + + toggleBlackBoxing(gPanel, getSourceActor(gSources, EXAMPLE_URL + "code_blackboxing_one.js")); + toggleBlackBoxing(gPanel, getSourceActor(gSources, EXAMPLE_URL + "code_blackboxing_two.js")); + toggleBlackBoxing(gPanel, getSourceActor(gSources, EXAMPLE_URL + "code_blackboxing_three.js")); + return finished; +} + +function testBlackBoxStack() { + let finished = waitForSourceAndCaretAndScopes(gPanel, ".html", 21).then(() => { + is(gFrames.itemCount, 4, + "Should get 4 frames (one -> two -> three -> doDebuggerStatement)."); + is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 3, + "And 'one', 'two', and 'three' should each have their own black boxed frame."); + }); + + callInTab(gTab, "one"); + return finished; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gFrames = null; + gSources = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-05.js new file mode 100644 index 000000000..96e2b9873 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-05.js @@ -0,0 +1,74 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that a "this source is blackboxed" message is shown when necessary + * and can be properly dismissed. + */ + +const TAB_URL = EXAMPLE_URL + "doc_binary_search.html"; + +var gTab, gPanel, gDebugger; +var gDeck; + +function test() { + let options = { + source: EXAMPLE_URL + "code_binary_search.coffee", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gDeck = gDebugger.document.getElementById("editor-deck"); + + testSourceEditorShown(); + toggleBlackBoxing(gPanel) + .then(testBlackBoxMessageShown) + .then(clickStopBlackBoxingButton) + .then(testSourceEditorShownAgain) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testSourceEditorShown() { + is(gDeck.selectedIndex, "0", + "The first item in the deck should be selected (the source editor)."); +} + +function testBlackBoxMessageShown() { + is(gDeck.selectedIndex, "1", + "The second item in the deck should be selected (the black box message)."); +} + +function clickStopBlackBoxingButton() { + // Give the test a chance to finish before triggering the click event. + executeSoon(() => getEditorBlackboxMessageButton().click()); + return waitForDispatch(gPanel, gDebugger.constants.BLACKBOX); +} + +function testSourceEditorShownAgain() { + // Wait a tick for the final check to make sure the frontend's click handlers + // have finished. + return new Promise(resolve => { + is(gDeck.selectedIndex, "0", + "The first item in the deck should be selected again (the source editor)."); + resolve(); + }); +} + +function getEditorBlackboxMessageButton() { + return gDebugger.document.getElementById("black-boxed-message-button"); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gDeck = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-06.js new file mode 100644 index 000000000..23a13f4db --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-06.js @@ -0,0 +1,61 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that clicking the black box checkbox when paused doesn't re-select the + * currently paused frame's source. + */ + +const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html"; + +var gTab, gPanel, gDebugger; +var gSources; + +function test() { + let options = { + source: EXAMPLE_URL + "code_blackboxing_blackboxme.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + + waitForCaretAndScopes(gPanel, 21) + .then(testBlackBox) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + callInTab(gTab, "runTest"); + }); +} + +function testBlackBox() { + const selectedActor = gSources.selectedValue; + + let finished = waitForSourceShown(gPanel, "blackboxme.js").then(() => { + const newSelectedActor = gSources.selectedValue; + isnot(selectedActor, newSelectedActor, + "Should not have the same url selected."); + + return toggleBlackBoxing(gPanel).then(() => { + is(gSources.selectedValue, newSelectedActor, + "The selected source did not change."); + }); + }); + + gSources.selectedIndex = 0; + return finished; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gSources = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-07.js b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-07.js new file mode 100644 index 000000000..1aa6b0bd1 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-07.js @@ -0,0 +1,53 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that if we unblackbox a source which has been automatically blackboxed + * and then refresh, it is still unblackboxed. + */ + +const TAB_URL = EXAMPLE_URL + "doc_blackboxing_unblackbox.html"; + +var gTab, gPanel, gDebugger; + +function test() { + let options = { + source: EXAMPLE_URL + "code_blackboxing_unblackbox.min.js", + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + + testBlackBoxSource() + .then(testBlackBoxReload) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testBlackBoxSource() { + const bbButton = getBlackBoxButton(gPanel); + ok(bbButton.checked, "Should be black boxed by default"); + + return toggleBlackBoxing(gPanel).then(aSource => { + ok(!aSource.isBlackBoxed, "The source should no longer be blackboxed."); + }); +} + +function testBlackBoxReload() { + return reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(() => { + const selectedSource = getSelectedSourceElement(gPanel); + ok(!selectedSource.isBlackBoxed, "The source should not be blackboxed."); + }); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breadcrumbs-access.js b/devtools/client/debugger/test/mochitest/browser_dbg_breadcrumbs-access.js new file mode 100644 index 000000000..596bcd336 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breadcrumbs-access.js @@ -0,0 +1,98 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the stackframe breadcrumbs are keyboard accessible. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + let gTab, gPanel, gDebugger; + let gSources, gFrames; + + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + gFrames = gDebugger.DebuggerView.StackFrames; + + waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6) + .then(checkNavigationWhileNotFocused) + .then(focusCurrentStackFrame) + .then(checkNavigationWhileFocused) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + callInTab(gTab, "firstCall"); + }); + + function checkNavigationWhileNotFocused() { + checkState({ frame: 1, source: 1, line: 6 }); + + return Task.spawn(function* () { + EventUtils.sendKey("DOWN", gDebugger); + checkState({ frame: 1, source: 1, line: 7 }); + + EventUtils.sendKey("UP", gDebugger); + checkState({ frame: 1, source: 1, line: 6 }); + }); + } + + function focusCurrentStackFrame() { + EventUtils.sendMouseEvent({ type: "mousedown" }, + gFrames.selectedItem.target, + gDebugger); + } + + function checkNavigationWhileFocused() { + return Task.spawn(function* () { + yield promise.all([ + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES), + waitForSourceAndCaret(gPanel, "-01.js", 5), + EventUtils.sendKey("UP", gDebugger) + ]); + checkState({ frame: 0, source: 0, line: 5 }); + + // Need to refocus the stack frame due to a focus bug in e10s + // (See Bug 1205482) + focusCurrentStackFrame(); + + yield promise.all([ + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES), + waitForSourceAndCaret(gPanel, "-02.js", 6), + EventUtils.sendKey("END", gDebugger) + ]); + checkState({ frame: 1, source: 1, line: 6 }); + + // Need to refocus the stack frame due to a focus bug in e10s + // (See Bug 1205482) + focusCurrentStackFrame(); + + yield promise.all([ + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES), + waitForSourceAndCaret(gPanel, "-01.js", 5), + EventUtils.sendKey("HOME", gDebugger) + ]); + checkState({ frame: 0, source: 0, line: 5 }); + }); + } + + function checkState({ frame, source, line, column }) { + is(gFrames.selectedIndex, frame, + "The currently selected stackframe is incorrect."); + is(gSources.selectedIndex, source, + "The currently selected source is incorrect."); + ok(isCaretPos(gPanel, line, column), + "The source editor caret position was incorrect."); + } +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-in-anon.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-in-anon.js new file mode 100644 index 000000000..23d55f4b9 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-in-anon.js @@ -0,0 +1,40 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure anonymous eval scripts can still break with a `debugger` + * statement + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-eval.html"; + +function test() { + const options = { + source: EXAMPLE_URL + "code_script-eval.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + + return Task.spawn(function* () { + is(gSources.values.length, 1, "Should have 1 source"); + + callInTab(gTab, "evalSourceWithDebugger"); + yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN); + + is(gSources.values.length, 2, "Should have 2 sources"); + + let item = gSources.getItemForAttachment(e => e.label.indexOf("SCRIPT") === 0); + ok(item, "Source label is incorrect."); + is(item.attachment.group, gDebugger.L10N.getStr("anonymousSourcesLabel"), + "Source group is incorrect"); + + yield resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-01.js new file mode 100644 index 000000000..82481187a --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-01.js @@ -0,0 +1,58 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejections should be fixed. +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("[object Object]"); +thisTestLeaksUncaughtRejectionsAndShouldBeFixed( + "TypeError: this.transport is null"); + +/** + * Tests that event listeners aren't fetched when the events tab isn't selected. + */ + +const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + let gPanel = aPanel; + let gDebugger = aPanel.panelWin; + let gView = gDebugger.DebuggerView; + let gEvents = gView.EventListeners; + let gController = gDebugger.DebuggerController; + let constants = gDebugger.require("./content/constants"); + + gDebugger.on(gDebugger.EVENTS.EVENT_LISTENERS_FETCHED, () => { + ok(false, "Shouldn't have fetched any event listeners."); + }); + gDebugger.on(gDebugger.EVENTS.EVENT_BREAKPOINTS_UPDATED, () => { + ok(false, "Shouldn't have updated any event breakpoints."); + }); + + gView.toggleInstrumentsPane({ visible: true, animated: false }); + + is(gView.instrumentsPaneHidden, false, + "The instruments pane should be visible now."); + is(gView.instrumentsPaneTab, "variables-tab", + "The variables tab should be selected by default."); + + Task.spawn(function* () { + is(gEvents.itemCount, 0, "There should be no events before reloading."); + + let reloaded = waitForNavigation(gPanel); + gDebugger.DebuggerController._target.activeTab.reload(); + + is(gEvents.itemCount, 0, "There should be no events while reloading."); + yield reloaded; + is(gEvents.itemCount, 0, "There should be no events after reloading."); + + yield closeDebuggerAndFinish(aPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-02.js new file mode 100644 index 000000000..dcbb1dca1 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-02.js @@ -0,0 +1,135 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that event listeners are fetched when the events tab is selected + * or while sources are fetched and the events tab is focused. + */ + +const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + let gPanel = aPanel; + let gDebugger = aPanel.panelWin; + let gView = gDebugger.DebuggerView; + let gEvents = gView.EventListeners; + let gController = gDebugger.DebuggerController; + let constants = gDebugger.require("./content/constants"); + + Task.spawn(function* () { + yield testFetchOnFocus(); + yield testFetchOnReloadWhenFocused(); + yield testFetchOnReloadWhenNotFocused(); + yield closeDebuggerAndFinish(aPanel); + }); + + function testFetchOnFocus() { + return Task.spawn(function* () { + let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS); + + gView.toggleInstrumentsPane({ visible: true, animated: false }, 1); + is(gView.instrumentsPaneHidden, false, + "The instruments pane should be visible now."); + is(gView.instrumentsPaneTab, "events-tab", + "The events tab should be selected."); + + yield fetched; + + ok(true, + "Event listeners were fetched when the events tab was selected"); + is(gEvents.itemCount, 4, + "There should be 4 events displayed in the view."); + }); + } + + function testFetchOnReloadWhenFocused() { + return Task.spawn(function* () { + let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS); + + let reloading = once(gDebugger.gTarget, "will-navigate"); + let reloaded = waitForNavigation(gPanel); + gDebugger.DebuggerController._target.activeTab.reload(); + + yield reloading; + + is(gEvents.itemCount, 0, + "There should be no events displayed in the view while reloading."); + ok(true, + "Event listeners were removed when the target started navigating."); + + yield reloaded; + + is(gView.instrumentsPaneHidden, false, + "The instruments pane should still be visible."); + is(gView.instrumentsPaneTab, "events-tab", + "The events tab should still be selected."); + + yield fetched; + + is(gEvents.itemCount, 4, + "There should be 4 events displayed in the view after reloading."); + ok(true, + "Event listeners were added back after the target finished navigating."); + }); + } + + function testFetchOnReloadWhenNotFocused() { + return Task.spawn(function* () { + gController.dispatch({ + type: gDebugger.services.WAIT_UNTIL, + predicate: action => { + return (action.type === constants.FETCH_EVENT_LISTENERS || + action.type === constants.UPDATE_EVENT_BREAKPOINTS); + }, + run: (dispatch, getState, action) => { + if (action.type === constants.FETCH_EVENT_LISTENERS) { + ok(false, "Shouldn't have fetched any event listeners."); + } + else if (action.type === constants.UPDATE_EVENT_BREAKPOINTS) { + ok(false, "Shouldn't have updated any event breakpoints."); + } + } + }); + + gView.toggleInstrumentsPane({ visible: true, animated: false }, 0); + is(gView.instrumentsPaneHidden, false, + "The instruments pane should still be visible."); + is(gView.instrumentsPaneTab, "variables-tab", + "The variables tab should be selected."); + + let reloading = once(gDebugger.gTarget, "will-navigate"); + let reloaded = waitForNavigation(gPanel); + gDebugger.DebuggerController._target.activeTab.reload(); + + yield reloading; + + is(gEvents.itemCount, 0, + "There should be no events displayed in the view while reloading."); + ok(true, + "Event listeners were removed when the target started navigating."); + + yield reloaded; + + is(gView.instrumentsPaneHidden, false, + "The instruments pane should still be visible."); + is(gView.instrumentsPaneTab, "variables-tab", + "The variables tab should still be selected."); + + // Just to be really sure that the events will never ever fire. + yield waitForTime(1000); + + is(gEvents.itemCount, 0, + "There should be no events displayed in the view after reloading."); + ok(true, + "Event listeners were not added after the target finished navigating."); + }); + } + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-03.js new file mode 100644 index 000000000..6d0d9708c --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-03.js @@ -0,0 +1,102 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that event listeners are properly displayed in the view. + */ + +const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + let gDebugger = aPanel.panelWin; + let gView = gDebugger.DebuggerView; + let gEvents = gView.EventListeners; + let gController = gDebugger.DebuggerController; + let constants = gDebugger.require("./content/constants"); + + Task.spawn(function* () { + let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS); + gView.toggleInstrumentsPane({ visible: true, animated: false }, 1); + yield fetched; + + is(gEvents.widget._parent.querySelectorAll(".side-menu-widget-group").length, 3, + "There should be 3 groups shown in the view."); + + let groupCheckboxes = gEvents.widget._parent.querySelectorAll( + ".side-menu-widget-group-checkbox"); + is(groupCheckboxes.length, 3, + "There should be a checkbox for each group shown in the view."); + for (let cb of groupCheckboxes) { + isnot(cb.getAttribute("tooltiptext"), "undefined", + "A valid tooltip text should be defined on group checkboxes"); + } + + is(gEvents.widget._parent.querySelectorAll(".side-menu-widget-item").length, 4, + "There should be 4 items shown in the view."); + is(gEvents.widget._parent.querySelectorAll(".side-menu-widget-item-checkbox").length, 4, + "There should be a checkbox for each item shown in the view."); + + testEventItem(0, "doc_event-listeners-02.html", "change", ["body > input:nth-child(2)"], false); + testEventItem(1, "doc_event-listeners-02.html", "click", ["body > button:nth-child(1)"], false); + testEventItem(2, "doc_event-listeners-02.html", "keydown", ["window", "body"], false); + testEventItem(3, "doc_event-listeners-02.html", "keyup", ["body > input:nth-child(2)"], false); + + testEventGroup("interactionEvents", false); + testEventGroup("keyboardEvents", false); + testEventGroup("mouseEvents", false); + + is(gEvents.getAllEvents().toString(), "change,click,keydown,keyup", + "The getAllEvents() method returns the correct stuff."); + is(gEvents.getCheckedEvents().toString(), "", + "The getCheckedEvents() method returns the correct stuff."); + + yield ensureThreadClientState(aPanel, "attached"); + yield closeDebuggerAndFinish(aPanel); + }); + + function testEventItem(index, label, type, selectors, checked) { + let item = gEvents.items[index]; + let node = item.target; + + ok(item.attachment.url.includes(label), + "The event at index " + index + " has the correct url."); + is(item.attachment.type, type, + "The event at index " + index + " has the correct type."); + is(item.attachment.selectors.toString(), selectors, + "The event at index " + index + " has the correct selectors."); + is(item.attachment.checkboxState, checked, + "The event at index " + index + " has the correct checkbox state."); + + let targets = selectors.length > 1 + ? gDebugger.L10N.getFormatStr("eventNodes", selectors.length) + : selectors.toString(); + + is(node.querySelector(".dbg-event-listener-type").getAttribute("value"), type, + "The correct type is shown for this event."); + is(node.querySelector(".dbg-event-listener-targets").getAttribute("value"), targets, + "The correct target is shown for this event."); + is(node.querySelector(".dbg-event-listener-location").getAttribute("value"), label, + "The correct location is shown for this event."); + is(node.parentNode.querySelector(".side-menu-widget-item-checkbox").checked, checked, + "The correct checkbox state is shown for this event."); + } + + function testEventGroup(string, checked) { + let name = gDebugger.L10N.getStr(string); + let group = gEvents.widget._parent + .querySelector(".side-menu-widget-group[name=" + name + "]"); + + is(group.querySelector(".side-menu-widget-group-title > .name").value, name, + "The correct label is shown for the group named " + name + "."); + is(group.querySelector(".side-menu-widget-group-checkbox").checked, checked, + "The correct checkbox state is shown for the group named " + name + "."); + } + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-04.js new file mode 100644 index 000000000..3f2b2948e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-04.js @@ -0,0 +1,101 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that checking/unchecking an event listener in the view correctly + * causes the active thread to get updated with the new event breakpoints. + */ + +const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + let gDebugger = aPanel.panelWin; + let gView = gDebugger.DebuggerView; + let gController = gDebugger.DebuggerController; + let gEvents = gView.EventListeners; + let constants = gDebugger.require("./content/constants"); + + Task.spawn(function* () { + let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS); + gView.toggleInstrumentsPane({ visible: true, animated: false }, 1); + yield fetched; + + testEventItem(0, false); + testEventItem(1, false); + testEventItem(2, false); + testEventItem(3, false); + testEventGroup("interactionEvents", false); + testEventGroup("keyboardEvents", false); + testEventGroup("mouseEvents", false); + testEventArrays("change,click,keydown,keyup", ""); + + let updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS); + EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger); + yield updated; + + testEventItem(0, true); + testEventItem(1, false); + testEventItem(2, false); + testEventItem(3, false); + testEventGroup("interactionEvents", false); + testEventGroup("keyboardEvents", false); + testEventGroup("mouseEvents", false); + testEventArrays("change,click,keydown,keyup", "change"); + + updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS); + EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger); + yield updated; + + testEventItem(0, false); + testEventItem(1, false); + testEventItem(2, false); + testEventItem(3, false); + testEventGroup("interactionEvents", false); + testEventGroup("keyboardEvents", false); + testEventGroup("mouseEvents", false); + testEventArrays("change,click,keydown,keyup", ""); + + yield ensureThreadClientState(aPanel, "attached"); + yield closeDebuggerAndFinish(aPanel); + }); + + function getItemCheckboxNode(index) { + return gEvents.items[index].target.parentNode + .querySelector(".side-menu-widget-item-checkbox"); + } + + function getGroupCheckboxNode(string) { + return gEvents.widget._parent + .querySelector(".side-menu-widget-group[name=" + gDebugger.L10N.getStr(string) + "]") + .querySelector(".side-menu-widget-group-checkbox"); + } + + function testEventItem(index, checked) { + is(gEvents.attachments[index].checkboxState, checked, + "The event at index " + index + " has the correct checkbox state."); + is(getItemCheckboxNode(index).checked, checked, + "The correct checkbox state is shown for this event."); + } + + function testEventGroup(string, checked) { + is(getGroupCheckboxNode(string).checked, checked, + "The correct checkbox state is shown for the group " + string + "."); + } + + function testEventArrays(all, checked) { + is(gEvents.getAllEvents().toString(), all, + "The getAllEvents() method returns the correct stuff."); + is(gEvents.getCheckedEvents().toString(), checked, + "The getCheckedEvents() method returns the correct stuff."); + is(gController.getState().eventListeners.activeEventNames.toString(), checked, + "The correct event names are listed as being active breakpoints."); + } + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-05.js new file mode 100644 index 000000000..d0a552e81 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-05.js @@ -0,0 +1,128 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that checking/unchecking an event listener's group in the view will + * cause the active thread to get updated with the new event breakpoints for + * all children inside that group. + */ + +const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + let gDebugger = aPanel.panelWin; + let gView = gDebugger.DebuggerView; + let gController = gDebugger.DebuggerController; + let gEvents = gView.EventListeners; + let constants = gDebugger.require("./content/constants"); + + Task.spawn(function* () { + let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS); + gView.toggleInstrumentsPane({ visible: true, animated: false }, 1); + yield fetched; + + testEventItem(0, false); + testEventItem(1, false); + testEventItem(2, false); + testEventItem(3, false); + testEventGroup("interactionEvents", false); + testEventGroup("keyboardEvents", false); + testEventGroup("mouseEvents", false); + testEventArrays("change,click,keydown,keyup", ""); + + let updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS); + EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("interactionEvents"), gDebugger); + yield updated; + + testEventItem(0, true); + testEventItem(1, false); + testEventItem(2, false); + testEventItem(3, false); + testEventGroup("interactionEvents", true); + testEventGroup("keyboardEvents", false); + testEventGroup("mouseEvents", false); + testEventArrays("change,click,keydown,keyup", "change"); + + updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS); + EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("interactionEvents"), gDebugger); + yield updated; + + testEventItem(0, false); + testEventItem(1, false); + testEventItem(2, false); + testEventItem(3, false); + testEventGroup("interactionEvents", false); + testEventGroup("keyboardEvents", false); + testEventGroup("mouseEvents", false); + testEventArrays("change,click,keydown,keyup", ""); + + updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS); + EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("keyboardEvents"), gDebugger); + yield updated; + + testEventItem(0, false); + testEventItem(1, false); + testEventItem(2, true); + testEventItem(3, true); + testEventGroup("interactionEvents", false); + testEventGroup("keyboardEvents", true); + testEventGroup("mouseEvents", false); + testEventArrays("change,click,keydown,keyup", "keydown,keyup"); + + updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS); + EventUtils.sendMouseEvent({ type: "click" }, getGroupCheckboxNode("keyboardEvents"), gDebugger); + yield updated; + + testEventItem(0, false); + testEventItem(1, false); + testEventItem(2, false); + testEventItem(3, false); + testEventGroup("interactionEvents", false); + testEventGroup("keyboardEvents", false); + testEventGroup("mouseEvents", false); + testEventArrays("change,click,keydown,keyup", ""); + + yield ensureThreadClientState(aPanel, "attached"); + yield closeDebuggerAndFinish(aPanel); + }); + + function getItemCheckboxNode(index) { + return gEvents.items[index].target.parentNode + .querySelector(".side-menu-widget-item-checkbox"); + } + + function getGroupCheckboxNode(string) { + return gEvents.widget._parent + .querySelector(".side-menu-widget-group[name=" + gDebugger.L10N.getStr(string) + "]") + .querySelector(".side-menu-widget-group-checkbox"); + } + + function testEventItem(index, checked) { + is(gEvents.attachments[index].checkboxState, checked, + "The event at index " + index + " has the correct checkbox state."); + is(getItemCheckboxNode(index).checked, checked, + "The correct checkbox state is shown for this event."); + } + + function testEventGroup(string, checked) { + is(getGroupCheckboxNode(string).checked, checked, + "The correct checkbox state is shown for the group " + string + "."); + } + + function testEventArrays(all, checked) { + is(gEvents.getAllEvents().toString(), all, + "The getAllEvents() method returns the correct stuff."); + is(gEvents.getCheckedEvents().toString(), checked, + "The getCheckedEvents() method returns the correct stuff."); + is(gController.getState().eventListeners.activeEventNames.toString(), checked, + "The correct event names are listed as being active breakpoints."); + } + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-06.js new file mode 100644 index 000000000..3197b640a --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-06.js @@ -0,0 +1,130 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the event listener states are preserved in the view after the + * target navigates. + */ + +const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + let gDebugger = aPanel.panelWin; + let gView = gDebugger.DebuggerView; + let gController = gDebugger.DebuggerController; + let gEvents = gView.EventListeners; + let gBreakpoints = gController.Breakpoints; + let constants = gDebugger.require("./content/constants"); + + Task.spawn(function* () { + let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS); + gView.toggleInstrumentsPane({ visible: true, animated: false }, 1); + yield fetched; + + testEventItem(0, false); + testEventItem(1, false); + testEventItem(2, false); + testEventItem(3, false); + testEventGroup("interactionEvents", false); + testEventGroup("keyboardEvents", false); + testEventGroup("mouseEvents", false); + testEventArrays("change,click,keydown,keyup", ""); + + let updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS); + EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger); + EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger); + EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(2), gDebugger); + yield updated; + + testEventItem(0, true); + testEventItem(1, true); + testEventItem(2, true); + testEventItem(3, false); + testEventGroup("interactionEvents", false); + testEventGroup("keyboardEvents", false); + testEventGroup("mouseEvents", false); + testEventArrays("change,click,keydown,keyup", "change,click,keydown"); + + reload(aPanel); + yield waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS); + + testEventItem(0, true); + testEventItem(1, true); + testEventItem(2, true); + testEventItem(3, false); + testEventGroup("interactionEvents", false); + testEventGroup("keyboardEvents", false); + testEventGroup("mouseEvents", false); + testEventArrays("change,click,keydown,keyup", "change,click,keydown"); + + updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS); + EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(0), gDebugger); + EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger); + EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(2), gDebugger); + yield updated; + + testEventItem(0, false); + testEventItem(1, false); + testEventItem(2, false); + testEventItem(3, false); + testEventGroup("interactionEvents", false); + testEventGroup("keyboardEvents", false); + testEventGroup("mouseEvents", false); + testEventArrays("change,click,keydown,keyup", ""); + + reload(aPanel); + yield waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS); + + testEventItem(0, false); + testEventItem(1, false); + testEventItem(2, false); + testEventItem(3, false); + testEventGroup("interactionEvents", false); + testEventGroup("keyboardEvents", false); + testEventGroup("mouseEvents", false); + testEventArrays("change,click,keydown,keyup", ""); + + yield ensureThreadClientState(aPanel, "attached"); + yield closeDebuggerAndFinish(aPanel); + }); + + function getItemCheckboxNode(index) { + return gEvents.items[index].target.parentNode + .querySelector(".side-menu-widget-item-checkbox"); + } + + function getGroupCheckboxNode(string) { + return gEvents.widget._parent + .querySelector(".side-menu-widget-group[name=" + gDebugger.L10N.getStr(string) + "]") + .querySelector(".side-menu-widget-group-checkbox"); + } + + function testEventItem(index, checked) { + is(gEvents.attachments[index].checkboxState, checked, + "The event at index " + index + " has the correct checkbox state."); + is(getItemCheckboxNode(index).checked, checked, + "The correct checkbox state is shown for this event."); + } + + function testEventGroup(string, checked) { + is(getGroupCheckboxNode(string).checked, checked, + "The correct checkbox state is shown for the group " + string + "."); + } + + function testEventArrays(all, checked) { + is(gEvents.getAllEvents().toString(), all, + "The getAllEvents() method returns the correct stuff."); + is(gEvents.getCheckedEvents().toString(), checked, + "The getCheckedEvents() method returns the correct stuff."); + is(gController.getState().eventListeners.activeEventNames.toString(), checked, + "The correct event names are listed as being active breakpoints."); + } + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-07.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-07.js new file mode 100644 index 000000000..107eab5f8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-07.js @@ -0,0 +1,106 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that system event listeners don't get duplicated in the view. + */ + +function test() { + initDebugger().then(([aTab,, aPanel]) => { + let gDebugger = aPanel.panelWin; + let gView = gDebugger.DebuggerView; + let gEvents = gView.EventListeners; + let gL10N = gDebugger.L10N; + + is(gEvents.itemCount, 0, + "There are no events displayed in the corresponding pane yet."); + + gEvents.addListener({ + type: "foo", + node: { selector: "#first" }, + function: { url: null } + }); + + is(gEvents.itemCount, 1, + "There was a system event listener added in the view."); + is(gEvents.attachments[0].url, gL10N.getStr("eventNative"), + "The correct string is used as the event's url."); + is(gEvents.attachments[0].type, "foo", + "The correct string is used as the event's type."); + is(gEvents.attachments[0].selectors.toString(), "#first", + "The correct array of selectors is used as the event's target."); + + gEvents.addListener({ + type: "bar", + node: { selector: "#second" }, + function: { url: null } + }); + + is(gEvents.itemCount, 2, + "There was another system event listener added in the view."); + is(gEvents.attachments[1].url, gL10N.getStr("eventNative"), + "The correct string is used as the event's url."); + is(gEvents.attachments[1].type, "bar", + "The correct string is used as the event's type."); + is(gEvents.attachments[1].selectors.toString(), "#second", + "The correct array of selectors is used as the event's target."); + + gEvents.addListener({ + type: "foo", + node: { selector: "#first" }, + function: { url: null } + }); + + is(gEvents.itemCount, 2, + "There wasn't another system event listener added in the view."); + is(gEvents.attachments[0].url, gL10N.getStr("eventNative"), + "The correct string is used as the event's url."); + is(gEvents.attachments[0].type, "foo", + "The correct string is used as the event's type."); + is(gEvents.attachments[0].selectors.toString(), "#first", + "The correct array of selectors is used as the event's target."); + + gEvents.addListener({ + type: "foo", + node: { selector: "#second" }, + function: { url: null } + }); + + is(gEvents.itemCount, 2, + "There still wasn't another system event listener added in the view."); + is(gEvents.attachments[0].url, gL10N.getStr("eventNative"), + "The correct string is used as the event's url."); + is(gEvents.attachments[0].type, "foo", + "The correct string is used as the event's type."); + is(gEvents.attachments[0].selectors.toString(), "#first,#second", + "The correct array of selectors is used as the event's target."); + + + gEvents.addListener({ + type: null, + node: { selector: "#bogus" }, + function: { url: null } + }); + + is(gEvents.itemCount, 2, + "No bogus system event listener was added in the view."); + + is(gEvents.attachments[0].url, gL10N.getStr("eventNative"), + "The correct string is used as the first event's url."); + is(gEvents.attachments[0].type, "foo", + "The correct string is used as the first event's type."); + is(gEvents.attachments[0].selectors.toString(), "#first,#second", + "The correct array of selectors is used as the first event's target."); + + is(gEvents.attachments[1].url, gL10N.getStr("eventNative"), + "The correct string is used as the second event's url."); + is(gEvents.attachments[1].type, "bar", + "The correct string is used as the second event's type."); + is(gEvents.attachments[1].selectors.toString(), "#second", + "The correct array of selectors is used as the second event's target."); + + closeDebuggerAndFinish(aPanel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-08.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-08.js new file mode 100644 index 000000000..c2bad132b --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-08.js @@ -0,0 +1,61 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that breaking on an event selects the variables view tab. + */ + +const TAB_URL = EXAMPLE_URL + "doc_event-listeners-02.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + let gTab = aTab; + let gDebugger = aPanel.panelWin; + let gView = gDebugger.DebuggerView; + let gEvents = gView.EventListeners; + let gController = gDebugger.DebuggerController; + let constants = gDebugger.require("./content/constants"); + + Task.spawn(function* () { + yield callInTab(gTab, "addBodyClickEventListener"); + + let fetched = waitForDispatch(aPanel, constants.FETCH_EVENT_LISTENERS); + gView.toggleInstrumentsPane({ visible: true, animated: false }, 1); + yield fetched; + yield ensureThreadClientState(aPanel, "attached"); + + is(gView.instrumentsPaneHidden, false, + "The instruments pane should be visible."); + is(gView.instrumentsPaneTab, "events-tab", + "The events tab should be selected."); + + let updated = waitForDispatch(aPanel, constants.UPDATE_EVENT_BREAKPOINTS); + EventUtils.sendMouseEvent({ type: "click" }, getItemCheckboxNode(1), gDebugger); + yield updated; + yield ensureThreadClientState(aPanel, "attached"); + + let paused = waitForCaretAndScopes(aPanel, 48); + generateMouseClickInTab(gTab, "content.document.body"); + yield paused; + yield ensureThreadClientState(aPanel, "paused"); + + is(gView.instrumentsPaneHidden, false, + "The instruments pane should be visible."); + is(gView.instrumentsPaneTab, "variables-tab", + "The variables tab should be selected."); + + yield resumeDebuggerThenCloseAndFinish(aPanel); + }); + + function getItemCheckboxNode(index) { + return gEvents.items[index].target.parentNode + .querySelector(".side-menu-widget-item-checkbox"); + } + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js new file mode 100644 index 000000000..a066b7d6b --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js @@ -0,0 +1,225 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the break-on-dom-events request works. + */ + +const TAB_URL = EXAMPLE_URL + "doc_event-listeners-01.html"; + +var gClient, gThreadClient, gInput, gButton; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + addTab(TAB_URL) + .then(() => attachThreadActorForUrl(gClient, TAB_URL)) + .then(setupGlobals) + .then(pauseDebuggee) + .then(testBreakOnAll) + .then(testBreakOnDisabled) + .then(testBreakOnNone) + .then(testBreakOnClick) + .then(() => gClient.close()) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function setupGlobals(aThreadClient) { + gThreadClient = aThreadClient; + gInput = content.document.querySelector("input"); + gButton = content.document.querySelector("button"); +} + +function pauseDebuggee() { + let deferred = promise.defer(); + + gClient.addOneTimeListener("paused", (aEvent, aPacket) => { + is(aPacket.type, "paused", + "We should now be paused."); + is(aPacket.why.type, "debuggerStatement", + "The debugger statement was hit."); + + deferred.resolve(); + }); + + // Spin the event loop before causing the debuggee to pause, to allow + // this function to return first. + executeSoon(triggerButtonClick); + + return deferred.promise; +} + +// Test pause on all events. +function testBreakOnAll() { + let deferred = promise.defer(); + + // Test calling pauseOnDOMEvents from a paused state. + gThreadClient.pauseOnDOMEvents("*", (aPacket) => { + is(aPacket.error, undefined, + "The pause-on-any-event request completed successfully."); + + gClient.addOneTimeListener("paused", (aEvent, aPacket) => { + is(aPacket.why.type, "pauseOnDOMEvents", + "A hidden breakpoint was hit."); + is(aPacket.frame.callee.name, "keyupHandler", + "The keyupHandler is entered."); + + gClient.addOneTimeListener("paused", (aEvent, aPacket) => { + is(aPacket.why.type, "pauseOnDOMEvents", + "A hidden breakpoint was hit."); + is(aPacket.frame.callee.name, "clickHandler", + "The clickHandler is entered."); + + gClient.addOneTimeListener("paused", (aEvent, aPacket) => { + is(aPacket.why.type, "pauseOnDOMEvents", + "A hidden breakpoint was hit."); + is(aPacket.frame.callee.name, "onchange", + "The onchange handler is entered."); + + gThreadClient.resume(deferred.resolve); + }); + + gThreadClient.resume(triggerInputChange); + }); + + gThreadClient.resume(triggerButtonClick); + }); + + gThreadClient.resume(triggerInputKeyup); + }); + + return deferred.promise; +} + +// Test that removing events from the array disables them. +function testBreakOnDisabled() { + let deferred = promise.defer(); + + // Test calling pauseOnDOMEvents from a running state. + gThreadClient.pauseOnDOMEvents(["click"], (aPacket) => { + is(aPacket.error, undefined, + "The pause-on-click-only request completed successfully."); + + gClient.addListener("paused", unexpectedListener); + + // This non-capturing event listener is guaranteed to run after the page's + // capturing one had a chance to execute and modify window.foobar. + once(gInput, "keyup").then(() => { + is(content.wrappedJSObject.foobar, "keyupHandler", + "No hidden breakpoint was hit."); + + gClient.removeListener("paused", unexpectedListener); + deferred.resolve(); + }); + + triggerInputKeyup(); + }); + + return deferred.promise; +} + +// Test that specifying an empty event array clears all hidden breakpoints. +function testBreakOnNone() { + let deferred = promise.defer(); + + // Test calling pauseOnDOMEvents from a running state. + gThreadClient.pauseOnDOMEvents([], (aPacket) => { + is(aPacket.error, undefined, + "The pause-on-none request completed successfully."); + + gClient.addListener("paused", unexpectedListener); + + // This non-capturing event listener is guaranteed to run after the page's + // capturing one had a chance to execute and modify window.foobar. + once(gInput, "keyup").then(() => { + is(content.wrappedJSObject.foobar, "keyupHandler", + "No hidden breakpoint was hit."); + + gClient.removeListener("paused", unexpectedListener); + deferred.resolve(); + }); + + triggerInputKeyup(); + }); + + return deferred.promise; +} + +// Test pause on a single event. +function testBreakOnClick() { + let deferred = promise.defer(); + + // Test calling pauseOnDOMEvents from a running state. + gThreadClient.pauseOnDOMEvents(["click"], (aPacket) => { + is(aPacket.error, undefined, + "The pause-on-click request completed successfully."); + + gClient.addOneTimeListener("paused", (aEvent, aPacket) => { + is(aPacket.why.type, "pauseOnDOMEvents", + "A hidden breakpoint was hit."); + is(aPacket.frame.callee.name, "clickHandler", + "The clickHandler is entered."); + + gThreadClient.resume(deferred.resolve); + }); + + triggerButtonClick(); + }); + + return deferred.promise; +} + +function unexpectedListener() { + gClient.removeListener("paused", unexpectedListener); + ok(false, "An unexpected hidden breakpoint was hit."); + gThreadClient.resume(testBreakOnClick); +} + +function triggerInputKeyup() { + // Make sure that the focus is not on the input box so that a focus event + // will be triggered. + window.focus(); + gBrowser.selectedBrowser.focus(); + gButton.focus(); + + // Focus the element and wait for focus event. + once(gInput, "focus").then(() => { + executeSoon(() => { + EventUtils.synthesizeKey("e", { shiftKey: 1 }, content); + }); + }); + + gInput.focus(); +} + +function triggerButtonClick() { + EventUtils.sendMouseEvent({ type: "click" }, gButton); +} + +function triggerInputChange() { + gInput.focus(); + gInput.value = "foo"; + gInput.blur(); +} + +registerCleanupFunction(function () { + gClient = null; + gThreadClient = null; + gInput = null; + gButton = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-02.js new file mode 100644 index 000000000..d6d502343 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-02.js @@ -0,0 +1,105 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the break-on-dom-events request works even for bound event + * listeners and handler objects with 'handleEvent' methods. + */ + +const TAB_URL = EXAMPLE_URL + "doc_event-listeners-03.html"; + +var gClient, gThreadClient; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + addTab(TAB_URL) + .then(() => attachThreadActorForUrl(gClient, TAB_URL)) + .then(aThreadClient => gThreadClient = aThreadClient) + .then(pauseDebuggee) + .then(testBreakOnClick) + .then(() => gClient.close()) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function pauseDebuggee() { + let deferred = promise.defer(); + + gClient.addOneTimeListener("paused", (aEvent, aPacket) => { + is(aPacket.type, "paused", + "We should now be paused."); + is(aPacket.why.type, "debuggerStatement", + "The debugger statement was hit."); + + gThreadClient.resume(deferred.resolve); + }); + + // Spin the event loop before causing the debuggee to pause, to allow + // this function to return first. + executeSoon(() => triggerButtonClick("initialSetup")); + + return deferred.promise; +} + +// Test pause on a single event. +function testBreakOnClick() { + let deferred = promise.defer(); + + // Test calling pauseOnDOMEvents from a running state. + gThreadClient.pauseOnDOMEvents(["click"], (aPacket) => { + is(aPacket.error, undefined, + "The pause-on-click request completed successfully."); + let handlers = ["clicker"]; + + gClient.addListener("paused", function tester(aEvent, aPacket) { + is(aPacket.why.type, "pauseOnDOMEvents", + "A hidden breakpoint was hit."); + + switch (handlers.length) { + case 1: + is(aPacket.frame.where.line, 26, "Found the clicker handler."); + handlers.push("handleEventClick"); + break; + case 2: + is(aPacket.frame.where.line, 36, "Found the handleEventClick handler."); + handlers.push("boundHandleEventClick"); + break; + case 3: + is(aPacket.frame.where.line, 46, "Found the boundHandleEventClick handler."); + gClient.removeListener("paused", tester); + deferred.resolve(); + } + + gThreadClient.resume(() => triggerButtonClick(handlers.slice(-1))); + }); + + triggerButtonClick(handlers.slice(-1)); + }); + + return deferred.promise; +} + +function triggerButtonClick(aNodeId) { + let button = content.document.getElementById(aNodeId); + EventUtils.sendMouseEvent({ type: "click" }, button); +} + +registerCleanupFunction(function () { + gClient = null; + gThreadClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-03.js new file mode 100644 index 000000000..3ffe830e9 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-03.js @@ -0,0 +1,97 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the break-on-dom-events request works for load event listeners. + */ + +const TAB_URL = EXAMPLE_URL + "doc_event-listeners-04.html"; + +var gClient, gThreadClient; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + addTab(TAB_URL) + .then(() => attachThreadActorForUrl(gClient, TAB_URL)) + .then(aThreadClient => gThreadClient = aThreadClient) + .then(pauseDebuggee) + .then(testBreakOnLoad) + .then(() => gClient.close()) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function pauseDebuggee() { + let deferred = promise.defer(); + + gClient.addOneTimeListener("paused", (aEvent, aPacket) => { + is(aPacket.type, "paused", + "We should now be paused."); + is(aPacket.why.type, "debuggerStatement", + "The debugger statement was hit."); + + gThreadClient.resume(deferred.resolve); + }); + + // Spin the event loop before causing the debuggee to pause, to allow + // this function to return first. + executeSoon(() => triggerButtonClick()); + + return deferred.promise; +} + +// Test pause on a load event. +function testBreakOnLoad() { + let deferred = promise.defer(); + + // Test calling pauseOnDOMEvents from a running state. + gThreadClient.pauseOnDOMEvents(["load"], (aPacket) => { + is(aPacket.error, undefined, + "The pause-on-load request completed successfully."); + let handlers = ["loadHandler"]; + + gClient.addListener("paused", function tester(aEvent, aPacket) { + is(aPacket.why.type, "pauseOnDOMEvents", + "A hidden breakpoint was hit."); + + is(aPacket.frame.where.line, 15, "Found the load event listener."); + gClient.removeListener("paused", tester); + deferred.resolve(); + + gThreadClient.resume(() => triggerButtonClick(handlers.slice(-1))); + }); + + getTabActorForUrl(gClient, TAB_URL).then(aGrip => { + gClient.attachTab(aGrip.actor, (aResponse, aTabClient) => { + aTabClient.reload(); + }); + }); + }); + + return deferred.promise; +} + +function triggerButtonClick() { + let button = content.document.querySelector("button"); + EventUtils.sendMouseEvent({ type: "click" }, button); +} + +registerCleanupFunction(function () { + gClient = null; + gThreadClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next-console.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next-console.js new file mode 100644 index 000000000..c30694520 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next-console.js @@ -0,0 +1,61 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test if 'break on next' functionality works from executions + * in content triggered by the console in the toolbox. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-eval.html"; + +function test() { + let gTab, gPanel, gDebugger; + let gSources, gBreakpoints, gTarget, gResumeButton, gResumeKey, gThreadClient; + + let options = { + source: EXAMPLE_URL + "code_script-eval.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + gTarget = gDebugger.gTarget; + gThreadClient = gDebugger.gThreadClient; + gResumeButton = gDebugger.document.getElementById("resume"); + gResumeKey = gDebugger.document.getElementById("resumeKey"); + + testConsole() + .then(() => closeDebuggerAndFinish(gPanel)); + }); + + let testConsole = Task.async(function* () { + info("Starting testConsole"); + + let oncePaused = gTarget.once("thread-paused"); + EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger); + let jsterm = yield getSplitConsole(gDevTools.getToolbox(gPanel.target)); + let executed = jsterm.execute("1+1"); + yield oncePaused; + + let updatedFrame = yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES); + let variables = gDebugger.DebuggerView.Variables; + + is(variables._store.length, 3, "Correct number of scopes available"); + is(variables.getScopeAtIndex(0).name, "With scope [Object]", + "Paused with correct scope (0)"); + is(variables.getScopeAtIndex(1).name, "Block scope", + "Paused with correct scope (1)"); + is(variables.getScopeAtIndex(2).name, "Global scope [Window]", + "Paused with correct scope (2)"); + + let onceResumed = gTarget.once("thread-resumed"); + EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger); + yield onceResumed; + + yield executed; + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next.js new file mode 100644 index 000000000..53f03c183 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-next.js @@ -0,0 +1,103 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test if 'break on next' functionality works from executions + * in content that are triggered by the page. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-eval.html"; + +function test() { + let gTab, gPanel, gDebugger; + let gSources, gBreakpoints, gTarget, gResumeButton, gResumeKey, gThreadClient; + + const options = { + source: EXAMPLE_URL + "code_script-eval.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + gTarget = gDebugger.gTarget; + gThreadClient = gDebugger.gThreadClient; + gResumeButton = gDebugger.document.getElementById("resume"); + gResumeKey = gDebugger.document.getElementById("resumeKey"); + + testInterval() + .then(testEvent) + .then(() => closeDebuggerAndFinish(gPanel)); + }); + + // Testing an interval instead of a timeout / rAF because + // it's less likely to fail due to timing issues. If the + // first callback happens to fire before the break request + // happens then we'll just get it next time. + let testInterval = Task.async(function* () { + info("Starting testInterval"); + + yield evalInTab(gTab, ` + var interval = setInterval(function() { + return 1+1; + }, 100); + `); + + let oncePaused = gTarget.once("thread-paused"); + EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger); + yield oncePaused; + + yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN); + let variables = gDebugger.DebuggerView.Variables; + + is(variables._store.length, 3, "Correct number of scopes available"); + is(variables.getScopeAtIndex(0).name, "Function scope [interval<]", + "Paused with correct scope (0)"); + is(variables.getScopeAtIndex(1).name, "Block scope", + "Paused with correct scope (1)"); + is(variables.getScopeAtIndex(2).name, "Global scope [Window]", + "Paused with correct scope (2)"); + + yield evalInTab(gTab, "clearInterval(interval)"); + let onceResumed = gTarget.once("thread-resumed"); + EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger); + yield onceResumed; + }); + + let testEvent = Task.async(function* () { + info("Starting testEvent"); + + let oncePaused = gTarget.once("thread-paused"); + EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger); + once(gDebugger.gClient, "willInterrupt").then(() => { + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); + yield oncePaused; + + yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN); + let variables = gDebugger.DebuggerView.Variables; + + is(variables._store.length, 6, "Correct number of scopes available"); + is(variables.getScopeAtIndex(0).name, "Function scope [onclick]", + "Paused with correct scope (0)"); + // Non-syntactic lexical scope introduced by non-syntactic scope chain. + is(variables.getScopeAtIndex(1).name, "Block scope", + "Paused with correct scope (1)"); + is(variables.getScopeAtIndex(2).name, "With scope [HTMLButtonElement]", + "Paused with correct scope (2)"); + is(variables.getScopeAtIndex(3).name, "With scope [HTMLDocument]", + "Paused with correct scope (3)"); + // Global lexical scope. + is(variables.getScopeAtIndex(4).name, "Block scope", + "Paused with correct scope (4)"); + is(variables.getScopeAtIndex(5).name, "Global scope [Window]", + "Paused with correct scope (5)"); + + let onceResumed = gTarget.once("thread-resumed"); + EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger); + yield onceResumed; + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_break-unselected.js b/devtools/client/debugger/test/mochitest/browser_dbg_break-unselected.js new file mode 100644 index 000000000..b76a7606a --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-unselected.js @@ -0,0 +1,48 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test breaking in code and jumping to the debugger before + * the debugger UI has been initialized. + */ + +const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html"; + +function test() { + Task.spawn(function* () { + const tab = yield getTab(TAB_URL); + const target = TargetFactory.forTab(tab); + const toolbox = yield gDevTools.showToolbox(target, "webconsole"); + + is(toolbox.currentToolId, "webconsole", "Console is the current panel"); + + toolbox.target.on("thread-paused", Task.async(function* () { + // Wait for the toolbox to handle the event and switch tools + yield waitForTick(); + + is(toolbox.currentToolId, "jsdebugger", "Debugger is the current panel"); + + // Wait until it's actually fully loaded + yield toolbox.loadTool("jsdebugger"); + + const panel = toolbox.getCurrentPanel(); + const queries = panel.panelWin.require("./content/queries"); + const getState = panel.panelWin.DebuggerController.getState; + + is(panel.panelWin.gThreadClient.state, "paused", + "Thread is still paused"); + + yield waitForSourceAndCaret(panel, "debugger-statement.html", 16); + is(queries.getSelectedSource(getState()).url, TAB_URL, + "Selected source is the current tab url"); + is(queries.getSelectedSourceOpts(getState()).line, 16, + "Line 16 is highlighted in the editor"); + + resumeDebuggerThenCloseAndFinish(panel); + })); + + callInTab(tab, "runDebuggerStatement"); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location.js new file mode 100644 index 000000000..0f880b9cc --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location.js @@ -0,0 +1,55 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 737803: Setting a breakpoint in a line without code should move + * the icon to the actual location. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const gController = gDebugger.DebuggerController; + const constants = gDebugger.require("./content/constants"); + const queries = gDebugger.require("./content/queries"); + const actions = bindActionCreators(gPanel); + + Task.spawn(function* () { + yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1); + + is(queries.getBreakpoints(gController.getState()).length, 0, + "There are no breakpoints in the editor"); + + const response = yield actions.addBreakpoint({ + actor: gSources.selectedValue, line: 4 + }); + + ok(response.actualLocation, "has an actualLocation"); + is(response.actualLocation.line, 6, "moved to line 6"); + + is(queries.getBreakpoints(gController.getState()).length, 1, + "There is only one breakpoint in the editor"); + + ok(!queries.getBreakpoint(gController.getState(), { actor: gSources.selectedValue, line: 4 }), + "There isn't any breakpoint added on an invalid line."); + ok(queries.getBreakpoint(gController.getState(), { actor: gSources.selectedValue, line: 6 }), + "There isn't any breakpoint added on an invalid line."); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); + + callInTab(gTab, "firstCall"); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location2.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location2.js new file mode 100644 index 000000000..16082d2cc --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-actual-location2.js @@ -0,0 +1,88 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 1008372: Setting a breakpoint in a line without code should move + * the icon to the actual location, and if a breakpoint already exists + * on the new location don't duplicate + */ + +const TAB_URL = EXAMPLE_URL + "doc_breakpoint-move.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const gController = gDebugger.DebuggerController; + const actions = bindActionCreators(gPanel); + const constants = gDebugger.require("./content/constants"); + const queries = gDebugger.require("./content/queries"); + + function resumeAndTestBreakpoint(line) { + return Task.spawn(function* () { + let event = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES); + doResume(gPanel); + yield event; + testBreakpoint(line); + }); + } + + function testBreakpoint(line) { + let bp = gSources._selectedBreakpoint; + ok(bp, "There should be a selected breakpoint on line " + line); + is(bp.location.line, line, + "The breakpoint on line " + line + " was not hit"); + } + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretAndScopes(gPanel, 16); + callInTab(gTab, "ermahgerd"); + yield onCaretUpdated; + + is(queries.getBreakpoints(gController.getState()).length, 0, + "There are no breakpoints in the editor"); + + yield actions.addBreakpoint({ + actor: gSources.selectedValue, + line: 19 + }); + yield actions.addBreakpoint({ + actor: gSources.selectedValue, + line: 20 + }); + + const response = yield actions.addBreakpoint({ + actor: gSources.selectedValue, + line: 17 + }); + + is(response.actualLocation.line, 19, + "Breakpoint client line is new."); + + yield resumeAndTestBreakpoint(19); + + yield actions.removeBreakpoint({ + actor: gSources.selectedValue, + line: 19 + }); + + yield resumeAndTestBreakpoint(20); + yield doResume(gPanel); + + callInTab(gTab, "ermahgerd"); + yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES); + + yield resumeAndTestBreakpoint(20); + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js new file mode 100644 index 000000000..124f8b1c2 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js @@ -0,0 +1,116 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 978019: Setting a breakpoint on the last line of a Debugger.Script and + * reloading should still hit the breakpoint. + */ + +const TAB_URL = EXAMPLE_URL + "doc_breakpoints-break-on-last-line-of-script-on-reload.html"; +const CODE_URL = EXAMPLE_URL + "code_breakpoints-break-on-last-line-of-script-on-reload.js"; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(2); + + let gPanel, gDebugger, gThreadClient, gEvents, gSources; + + const options = { + source: CODE_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gThreadClient = gDebugger.gThreadClient; + gEvents = gDebugger.EVENTS; + gSources = gDebugger.DebuggerView.Sources; + const actions = bindActionCreators(gPanel); + + Task.spawn(function* () { + try { + + // Refresh and hit the debugger statement before the location we want to + // set our breakpoints. We have to pause before the breakpoint locations + // so that GC doesn't get a chance to kick in and collect the IIFE's + // script, which would causes us to receive a 'noScript' error from the + // server when we try to set the breakpoints. + const [paused, ] = yield promise.all([ + waitForThreadEvents(gPanel, "paused"), + reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN), + ]); + + is(paused.why.type, "debuggerStatement"); + + // Set our breakpoints. + const sourceActor = getSourceActor(gSources, CODE_URL); + yield promise.all([ + actions.addBreakpoint({ + actor: sourceActor, + line: 3 + }), + actions.addBreakpoint({ + actor: sourceActor, + line: 4 + }), + actions.addBreakpoint({ + actor: sourceActor, + line: 5 + }) + ]); + + // Refresh and hit the debugger statement again. + yield promise.all([ + reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN), + waitForCaretAndScopes(gPanel, 1) + ]); + + // And we should hit the breakpoints as we resume. + yield promise.all([ + doResume(gPanel), + waitForCaretAndScopes(gPanel, 3) + ]); + yield promise.all([ + doResume(gPanel), + waitForCaretAndScopes(gPanel, 4) + ]); + yield promise.all([ + doResume(gPanel), + waitForCaretAndScopes(gPanel, 5) + ]); + + // Clean up the breakpoints. + yield promise.all([ + actions.removeBreakpoint({ actor: sourceActor, line: 3 }), + actions.removeBreakpoint({ actor: sourceActor, line: 4 }), + actions.removeBreakpoint({ actor: sourceActor, line: 5 }) + ]); + + yield resumeDebuggerThenCloseAndFinish(gPanel); + + } catch (e) { + DevToolsUtils.reportException( + "browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js", + e + ); + ok(false); + } + }); + }); + + function setBreakpoint(location) { + let item = gSources.getItemByValue(getSourceActor(gSources, location.url)); + let source = gThreadClient.source(item.attachment.source); + + let deferred = promise.defer(); + source.setBreakpoint(location, ({ error, message }, bpClient) => { + if (error) { + deferred.reject(error + ": " + message); + } + deferred.resolve(bpClient); + }); + return deferred.promise; + } +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-01.js new file mode 100644 index 000000000..25bf1fe06 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-01.js @@ -0,0 +1,55 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test if the breakpoints toggle button works as advertised. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + function checkBreakpointsDisabled(isDisabled, total = 3) { + let breakpoints = gDebugger.queries.getBreakpoints(getState()); + + is(breakpoints.length, total, + "Breakpoints should still be set."); + is(breakpoints.filter(bp => bp.disabled === isDisabled).length, total, + "Breakpoints should be " + (isDisabled ? "disabled" : "enabled") + "."); + } + + Task.spawn(function* () { + yield actions.addBreakpoint({ actor: gSources.values[0], line: 5 }); + yield actions.addBreakpoint({ actor: gSources.values[1], line: 6 }); + yield actions.addBreakpoint({ actor: gSources.values[1], line: 7 }); + yield ensureThreadClientState(gPanel, "resumed"); + + gSources.toggleBreakpoints(); + yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 3); + checkBreakpointsDisabled(true); + + const finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.BREAKPOINT_ADDED, 3); + gSources.toggleBreakpoints(); + yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT, 3); + checkBreakpointsDisabled(false); + + if (gDebugger.gThreadClient.state !== "attached") { + yield waitForThreadEvents(gPanel, "resumed"); + } + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-02.js new file mode 100644 index 000000000..6a763bf7e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-button-02.js @@ -0,0 +1,64 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test if the breakpoints toggle button works as advertised when there are + * some breakpoints already disabled. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + function checkBreakpointsDisabled(isDisabled, total = 3) { + let breakpoints = gDebugger.queries.getBreakpoints(getState()); + + is(breakpoints.length, total, + "Breakpoints should still be set."); + is(breakpoints.filter(bp => bp.disabled === isDisabled).length, total, + "Breakpoints should be " + (isDisabled ? "disabled" : "enabled") + "."); + } + + Task.spawn(function* () { + yield promise.all([ + actions.addBreakpoint({ actor: gSources.values[0], line: 5 }), + actions.addBreakpoint({ actor: gSources.values[1], line: 6 }), + actions.addBreakpoint({ actor: gSources.values[1], line: 7 }) + ]); + if (gDebugger.gThreadClient.state !== "attached") { + yield waitForThreadEvents(gPanel, "resumed"); + } + + yield promise.all([ + actions.disableBreakpoint({ actor: gSources.values[0], line: 5 }), + actions.disableBreakpoint({ actor: gSources.values[1], line: 6 }) + ]); + + gSources.toggleBreakpoints(); + yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 1); + checkBreakpointsDisabled(true); + + gSources.toggleBreakpoints(); + yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT, 3); + checkBreakpointsDisabled(false); + + if (gDebugger.gThreadClient.state !== "attached") { + yield waitForThreadEvents(gPanel, "resumed"); + } + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-condition-thrown-message.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-condition-thrown-message.js new file mode 100644 index 000000000..cefd429d2 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-condition-thrown-message.js @@ -0,0 +1,107 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the message which breakpoint condition throws + * could be displayed on UI correctly + */ + +const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + function initialCheck(aCaretLine) { + let bp = gDebugger.queries.getBreakpoint(getState(), + { actor: gSources.values[0], line: aCaretLine }); + ok(bp, "There should be a breakpoint on line " + aCaretLine); + + let attachment = gSources._getBreakpoint(bp).attachment; + ok(attachment, + "There should be a breakpoint on line " + aCaretLine + " in the sources pane."); + + let thrownNode = attachment.view.container.querySelector(".dbg-breakpoint-condition-thrown-message"); + ok(thrownNode, + "The breakpoint item should contain a thrown message node."); + + ok(!attachment.view.container.classList.contains("dbg-breakpoint-condition-thrown"), + "The thrown message on line " + aCaretLine + " should be hidden when condition has not been evaluated."); + } + + function resumeAndTestThrownMessage(line) { + doResume(gPanel); + + return waitForCaretUpdated(gPanel, line).then(() => { + // Test that the thrown message is correctly shown. + let bp = gDebugger.queries.getBreakpoint( + getState(), + { actor: gSources.values[0], line: line } + ); + let attachment = gSources._getBreakpoint(bp).attachment; + ok(attachment.view.container.classList.contains("dbg-breakpoint-condition-thrown"), + "Message on line " + line + " should be shown when condition throws."); + }); + } + + function resumeAndTestNoThrownMessage(line) { + doResume(gPanel); + + return waitForCaretUpdated(gPanel, line).then(() => { + // test that the thrown message is correctly shown + let bp = gDebugger.queries.getBreakpoint( + getState(), + { actor: gSources.values[0], line: line } + ); + let attachment = gSources._getBreakpoint(bp).attachment; + ok(!attachment.view.container.classList.contains("dbg-breakpoint-condition-thrown"), + "Message on line " + line + " should be hidden if condition doesn't throw."); + }); + } + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretAndScopes(gPanel, 17); + callInTab(gTab, "ermahgerd"); + yield onCaretUpdated; + + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 18 }, " 1afff"); + // Close the popup because a SET_BREAKPOINT_CONDITION action is + // fired when it's closed, and it sets it on the currently + // selected breakpoint and we want to make sure it uses the + // current breakpoint. This isn't a problem outside of tests + // because any UI interaction will close the popup before the + // new breakpoint is added. + gSources._hideConditionalPopup(); + initialCheck(18); + + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 19 }, "true"); + gSources._hideConditionalPopup(); + initialCheck(19); + + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 20 }, "false"); + gSources._hideConditionalPopup(); + initialCheck(20); + + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 22 }, "randomVar"); + gSources._hideConditionalPopup(); + initialCheck(22); + + yield resumeAndTestThrownMessage(18); + yield resumeAndTestNoThrownMessage(19); + yield resumeAndTestThrownMessage(22); + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu-add.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu-add.js new file mode 100644 index 000000000..2b50d53aa --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu-add.js @@ -0,0 +1,84 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test adding breakpoints from the source editor context menu + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const gContextMenu = gDebugger.document.getElementById("sourceEditorContextMenu"); + const queries = gDebugger.require("./content/queries"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + Task.spawn(function* () { + yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1); + + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(queries.getSourceCount(getState()), 2, + "Found the expected number of sources."); + isnot(gEditor.getText().indexOf("debugger"), -1, + "The correct source was loaded initially."); + is(gSources.selectedValue, gSources.values[1], + "The correct source is selected."); + + ok(gContextMenu, + "The source editor's context menupopup is available."); + + gEditor.focus(); + gEditor.setSelection({ line: 1, ch: 0 }, { line: 1, ch: 10 }); + + gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false); + gEditor.emit("gutterClick", 6, 2); + + yield once(gContextMenu, "popupshown"); + is(queries.getBreakpoints(getState()).length, 0, "no breakpoints added"); + + let cmd = gContextMenu.querySelector("menuitem[command=addBreakpointCommand]"); + EventUtils.synthesizeMouseAtCenter(cmd, {}, gDebugger); + yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT); + + is(queries.getBreakpoints(getState()).length, 1, + "1 breakpoint correctly added"); + ok(queries.getBreakpoint(getState(), + { actor: gSources.values[1], line: 7 }), + "Breakpoint on line 7 exists"); + + gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false); + gEditor.emit("gutterClick", 7, 2); + + yield once(gContextMenu, "popupshown"); + is(queries.getBreakpoints(getState()).length, 1, + "1 breakpoint correctly added"); + + cmd = gContextMenu.querySelector("menuitem[command=addConditionalBreakpointCommand]"); + EventUtils.synthesizeMouseAtCenter(cmd, {}, gDebugger); + yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT); + + is(queries.getBreakpoints(getState()).length, 2, + "2 breakpoints correctly added"); + ok(queries.getBreakpoint(getState(), + { actor: gSources.values[1], line: 8 }), + "Breakpoint on line 8 exists"); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); + + callInTab(gTab, "firstCall"); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu.js new file mode 100644 index 000000000..913d32073 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-contextmenu.js @@ -0,0 +1,252 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test if the context menu associated with each breakpoint does what it should. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(2); + + Task.spawn(function* () { + const options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + const [gTab,, gPanel ] = yield initDebugger(TAB_URL, options); + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + const addBreakpoints = Task.async(function* () { + yield actions.addBreakpoint({ actor: gSources.values[0], line: 5 }); + yield actions.addBreakpoint({ actor: gSources.values[1], line: 6 }); + yield actions.addBreakpoint({ actor: gSources.values[1], line: 7 }); + yield actions.addBreakpoint({ actor: gSources.values[1], line: 8 }); + yield actions.addBreakpoint({ actor: gSources.values[1], line: 9 }); + yield ensureThreadClientState(gPanel, "resumed"); + gSources.highlightBreakpoint({ actor: gSources.values[1], line: 9 }); + }); + + const pauseAndCheck = Task.async(function* () { + let source = queries.getSelectedSource(getState()); + is(source.url, EXAMPLE_URL + "code_script-switching-02.js", + "The currently selected source is incorrect (1)."); + is(gSources.selectedIndex, 1, + "The currently selected source is incorrect (2)."); + ok(isCaretPos(gPanel, 9), + "The editor location is correct before pausing."); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + + return waitForSourceAndCaretAndScopes(gPanel, "-01.js", 5).then(() => { + let source = queries.getSelectedSource(getState()); + is(source.url, EXAMPLE_URL + "code_script-switching-01.js", + "The currently selected source is incorrect (3)."); + is(gSources.selectedIndex, 0, + "The currently selected source is incorrect (4)."); + ok(isCaretPos(gPanel, 5), + "The editor location is correct after pausing."); + }); + }); + + let initialChecks = Task.async(function* () { + for (let bp of queries.getBreakpoints(getState())) { + ok(bp.actor, "All breakpoint items should have an actor"); + ok(!bp.disabled, "All breakpoints should initially be enabled."); + + let prefix = "bp-cMenu-"; // "breakpoints context menu" + let identifier = queries.makeLocationId(bp.location); + let enableSelfId = prefix + "enableSelf-" + identifier + "-menuitem"; + let disableSelfId = prefix + "disableSelf-" + identifier + "-menuitem"; + + // Check to make sure that only the bp context menu is shown when right clicking + // this node (Bug 1159276). + let breakpointItem = gSources._getBreakpoint(bp); + let menu = gDebugger.document.getElementById("bp-mPop-" + identifier); + let contextMenuShown = once(gDebugger.document, "popupshown"); + EventUtils.synthesizeMouseAtCenter(breakpointItem.prebuiltNode, {type: "contextmenu", button: 2}, gDebugger); + let event = yield contextMenuShown; + is(event.originalTarget.id, menu.id, "The correct context menu was shown"); + let contextMenuHidden = once(gDebugger.document, "popuphidden"); + menu.hidePopup(); + yield contextMenuHidden; + + is(gDebugger.document.getElementById(enableSelfId).getAttribute("hidden"), "true", + "The 'Enable breakpoint' context menu item should initially be hidden'."); + ok(!gDebugger.document.getElementById(disableSelfId).hasAttribute("hidden"), + "The 'Disable breakpoint' context menu item should initially not be hidden'."); + + is(breakpointItem.attachment.view.checkbox.getAttribute("checked"), "true", + "All breakpoints should initially have a checked checkbox."); + } + }); + + const checkBreakpointToggleSelf = Task.async(function* (index) { + EventUtils.sendMouseEvent({ type: "click" }, + gDebugger.document.querySelectorAll(".dbg-breakpoint")[index], + gDebugger); + + let selectedBreakpoint = gSources._selectedBreakpoint; + let selectedBreakpointItem = gSources._getBreakpoint(selectedBreakpoint); + + ok(selectedBreakpoint.actor, + "Selected breakpoint should have an actor."); + ok(!selectedBreakpoint.disabled, + "The breakpoint should not be disabled yet (" + index + ")."); + + let prefix = "bp-cMenu-"; // "breakpoints context menu" + let identifier = queries.makeLocationId(selectedBreakpoint.location); + let enableSelfId = prefix + "enableSelf-" + identifier + "-menuitem"; + let disableSelfId = prefix + "disableSelf-" + identifier + "-menuitem"; + + is(gDebugger.document.getElementById(enableSelfId).getAttribute("hidden"), "true", + "The 'Enable breakpoint' context menu item should be hidden'."); + ok(!gDebugger.document.getElementById(disableSelfId).hasAttribute("hidden"), + "The 'Disable breakpoint' context menu item should not be hidden'."); + + ok(isCaretPos(gPanel, selectedBreakpoint.location.line), + "The source editor caret position was incorrect (" + index + ")."); + + // Test disabling this breakpoint. + gSources._onDisableSelf(selectedBreakpoint.location); + yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT); + + ok(!!queries.getBreakpoint(getState(), selectedBreakpoint.location).disabled, + "The breakpoint should be disabled."); + + ok(!gDebugger.document.getElementById(enableSelfId).hasAttribute("hidden"), + "The 'Enable breakpoint' context menu item should not be hidden'."); + is(gDebugger.document.getElementById(disableSelfId).getAttribute("hidden"), "true", + "The 'Disable breakpoint' context menu item should be hidden'."); + ok(!selectedBreakpointItem.attachment.view.checkbox.hasAttribute("checked"), + "The breakpoint should now be unchecked."); + + gSources._onEnableSelf(selectedBreakpoint.location); + yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT); + + ok(!queries.getBreakpoint(getState(), selectedBreakpoint.location).disabled, + "The breakpoint should be enabled."); + is(gDebugger.document.getElementById(enableSelfId).getAttribute("hidden"), "true", + "The 'Enable breakpoint' context menu item should be hidden'."); + ok(!gDebugger.document.getElementById(disableSelfId).hasAttribute("hidden"), + "The 'Disable breakpoint' context menu item should not be hidden'."); + ok(selectedBreakpointItem.attachment.view.checkbox.hasAttribute("checked"), + "The breakpoint should now be checked."); + }); + + const checkBreakpointToggleOthers = Task.async(function* (index) { + EventUtils.sendMouseEvent( + { type: "click" }, + gDebugger.document.querySelectorAll(".dbg-breakpoint")[index], + gDebugger + ); + + // Test disabling other breakpoints. + disableOthers(); + yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 4); + + let selectedBreakpoint = queries.getBreakpoint(getState(), gSources._selectedBreakpoint.location); + + ok(selectedBreakpoint.actor, + "There should be a breakpoint actor."); + ok(!selectedBreakpoint.disabled, + "The targetted breakpoint should not have been disabled (" + index + ")."); + + for (let bp of queries.getBreakpoints(getState())) { + if (bp !== selectedBreakpoint) { + ok(bp.disabled, + "Non-targetted breakpoints should have been disabled."); + } + } + + // Test re-enabling other breakpoints. + enableOthers(); + yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT, 4); + for (let bp of queries.getBreakpoints(getState())) { + ok(!bp.disabled, "All breakpoints should be enabled."); + } + + // Test disabling all breakpoints. + disableAll(); + yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 5); + for (let bp of queries.getBreakpoints(getState())) { + ok(!!bp.disabled, "All breakpoints should be disabled."); + } + + // Test re-enabling all breakpoints. + enableAll(); + yield waitForDispatch(gPanel, gDebugger.constants.ADD_BREAKPOINT, 5); + for (let bp of queries.getBreakpoints(getState())) { + ok(!bp.disabled, "All breakpoints should be enabled."); + } + }); + + const testDeleteAll = Task.async(function* () { + // Test deleting all breakpoints. + deleteAll(); + yield waitForDispatch(gPanel, gDebugger.constants.REMOVE_BREAKPOINT, 5); + + ok(!gSources._selectedBreakpoint, + "There should be no breakpoint available after removing all breakpoints."); + + for (let bp of queries.getBreakpoints(getState())) { + ok(false, "It's a trap!"); + } + }); + + function disableOthers() { + gSources._onDisableOthers(gSources._selectedBreakpoint.location); + } + function enableOthers() { + gSources._onEnableOthers(gSources._selectedBreakpoint.location); + } + function disableAll() { + gSources._onDisableAll(); + } + function enableAll() { + gSources._onEnableAll(); + } + function deleteAll() { + gSources._onDeleteAll(); + } + + yield addBreakpoints(); + yield initialChecks(); + yield checkBreakpointToggleSelf(0); + yield checkBreakpointToggleOthers(0); + yield checkBreakpointToggleSelf(1); + yield checkBreakpointToggleOthers(1); + yield checkBreakpointToggleSelf(2); + yield checkBreakpointToggleOthers(2); + yield checkBreakpointToggleSelf(3); + yield checkBreakpointToggleOthers(3); + yield checkBreakpointToggleSelf(4); + yield checkBreakpointToggleOthers(4); + yield testDeleteAll(); + + yield addBreakpoints(); + yield initialChecks(); + yield pauseAndCheck(); + yield checkBreakpointToggleSelf(0); + yield checkBreakpointToggleOthers(0); + yield checkBreakpointToggleSelf(1); + yield checkBreakpointToggleOthers(1); + yield checkBreakpointToggleSelf(2); + yield checkBreakpointToggleOthers(2); + yield checkBreakpointToggleSelf(3); + yield checkBreakpointToggleOthers(3); + yield checkBreakpointToggleSelf(4); + yield checkBreakpointToggleOthers(4); + yield testDeleteAll(); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-disabled-reload.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-disabled-reload.js new file mode 100644 index 000000000..1caf4994f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-disabled-reload.js @@ -0,0 +1,124 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that disabled breakpoints survive target navigation. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gPanel = aPanel; + const gTab = aTab; + const gDebugger = gPanel.panelWin; + const gEvents = gDebugger.EVENTS; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + let gBreakpointLocation; + + Task.spawn(function* () { + gBreakpointLocation = { actor: getSourceActor(gSources, EXAMPLE_URL + "code_script-switching-01.js"), + line: 5 }; + + yield actions.addBreakpoint(gBreakpointLocation); + + yield ensureThreadClientState(gPanel, "resumed"); + yield testWhenBreakpointEnabledAndFirstSourceShown(); + + gSources._preferredSourceURL = EXAMPLE_URL + "code_script-switching-02.js"; + yield reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN); + yield testWhenBreakpointEnabledAndSecondSourceShown(); + + yield actions.disableBreakpoint(gBreakpointLocation); + yield reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN); + + yield testWhenBreakpointDisabledAndSecondSourceShown(); + + yield actions.enableBreakpoint(gBreakpointLocation); + yield reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN); + yield testWhenBreakpointEnabledAndSecondSourceShown(); + + yield resumeDebuggerThenCloseAndFinish(gPanel); + }); + + function verifyView({ disabled }) { + return Task.spawn(function* () { + // It takes a tick for the checkbox in the SideMenuWidget and the + // gutter in the editor to get updated. + yield waitForTick(); + + let breakpoint = queries.getBreakpoint(getState(), gBreakpointLocation); + let breakpointItem = gSources._getBreakpoint(breakpoint); + is(!!breakpoint.disabled, disabled, + "The selected breakpoint state was correct."); + + is(breakpointItem.attachment.view.checkbox.hasAttribute("checked"), !disabled, + "The selected breakpoint's checkbox state was correct."); + }); + } + + // All the following executeSoon()'s are required to spin the event loop + // before causing the debuggee to pause, to allow functions to yield first. + + function testWhenBreakpointEnabledAndFirstSourceShown() { + return Task.spawn(function* () { + yield ensureSourceIs(gPanel, "-01.js"); + yield verifyView({ disabled: false }); + + callInTab(gTab, "firstCall"); + yield waitForDebuggerEvents(gPanel, gEvents.FETCHED_SCOPES); + yield ensureSourceIs(gPanel, "-01.js"); + yield ensureCaretAt(gPanel, 5); + yield verifyView({ disabled: false }); + + executeSoon(() => gDebugger.gThreadClient.resume()); + yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6); + yield verifyView({ disabled: false }); + }); + } + + function testWhenBreakpointEnabledAndSecondSourceShown() { + return Task.spawn(function* () { + yield ensureSourceIs(gPanel, "-02.js", true); + yield verifyView({ disabled: false }); + + callInTab(gTab, "firstCall"); + yield waitForSourceAndCaretAndScopes(gPanel, "-01.js", 1); + yield verifyView({ disabled: false }); + + executeSoon(() => gDebugger.gThreadClient.resume()); + yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6); + yield verifyView({ disabled: false }); + }); + } + + function testWhenBreakpointDisabledAndSecondSourceShown() { + return Task.spawn(function* () { + yield ensureSourceIs(gPanel, "-02.js", true); + yield verifyView({ disabled: true }); + + callInTab(gTab, "firstCall"); + yield waitForDebuggerEvents(gPanel, gEvents.FETCHED_SCOPES); + yield ensureSourceIs(gPanel, "-02.js"); + yield ensureCaretAt(gPanel, 6); + yield verifyView({ disabled: true }); + + executeSoon(() => gDebugger.gThreadClient.resume()); + yield waitForDebuggerEvents(gPanel, gEvents.AFTER_FRAMES_CLEARED); + yield ensureSourceIs(gPanel, "-02.js"); + yield ensureCaretAt(gPanel, 6); + yield verifyView({ disabled: true }); + }); + } + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-editor.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-editor.js new file mode 100644 index 000000000..d5232a50d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-editor.js @@ -0,0 +1,241 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 723069: Test the debugger breakpoint API and connection to the + * source editor. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + Task.spawn(function* () { + yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1); + + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(queries.getSourceCount(getState()), 2, + "Found the expected number of sources."); + is(gEditor.getText().indexOf("debugger"), 166, + "The correct source was loaded initially."); + is(queries.getSelectedSource(getState()).actor, gSources.values[1], + "The correct source is selected."); + + is(queries.getBreakpoints(getState()).length, 0, + "No breakpoints currently added."); + + info("Add the first breakpoint."); + gEditor.once("breakpointAdded", onEditorBreakpointAddFirst); + let location = { actor: gSources.selectedValue, line: 6 }; + yield actions.addBreakpoint(location); + checkFirstBreakpoint(location); + + info("Remove the first breakpoint."); + gEditor.once("breakpointRemoved", onEditorBreakpointRemoveFirst); + yield actions.removeBreakpoint(location); + checkFirstBreakpointRemoved(location); + checkBackgroundBreakpoint(yield testBreakpointAddBackground()); + + info("Switch to the first source, which is not yet selected"); + gEditor.once("breakpointAdded", onEditorBreakpointAddSwitch); + gEditor.once("change", onEditorTextChanged); + actions.selectSource(gSources.items[0].attachment.source); + yield waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN); + onReadyForClick(); + }); + + callInTab(gTab, "firstCall"); + + let breakpointsAdded = 0; + let breakpointsRemoved = 0; + let editorBreakpointChanges = 0; + + function onEditorBreakpointAddFirst(aEvent, aLine) { + editorBreakpointChanges++; + + ok(aEvent, + "breakpoint1 added to the editor."); + is(aLine, 5, + "Editor breakpoint line is correct."); + + is(gEditor.getBreakpoints().length, 1, + "editor.getBreakpoints().length is correct."); + } + + function onEditorBreakpointRemoveFirst(aEvent, aLine) { + editorBreakpointChanges++; + + ok(aEvent, + "breakpoint1 removed from the editor."); + is(aLine, 5, + "Editor breakpoint line is correct."); + + is(gEditor.getBreakpoints().length, 0, + "editor.getBreakpoints().length is correct."); + } + + function checkFirstBreakpoint(location) { + breakpointsAdded++; + const bp = queries.getBreakpoint(getState(), location); + + ok(bp, + "breakpoint1 exists"); + is(bp.location.actor, queries.getSelectedSource(getState()).actor, + "breakpoint1 actor is correct."); + is(bp.location.line, 6, + "breakpoint1 line is correct."); + + is(queries.getBreakpoints(getState()).length, 1, + "The list of added breakpoints holds only one breakpoint."); + + is(queries.getSelectedSource(getState()).actor, gSources.values[1], + "The second source should be currently selected."); + } + + function checkFirstBreakpointRemoved(location) { + breakpointsRemoved++; + const bp = queries.getBreakpoint(getState(), location); + ok(!bp, "breakpoint1 removed"); + } + + function testBreakpointAddBackground() { + is(queries.getBreakpoints(getState()).length, 0, + "No breakpoints currently added."); + + is(gSources.values[1], gSources.selectedValue, + "The second source should be currently selected."); + + info("Add a breakpoint to the first source, which is not selected."); + let location = { actor: gSources.values[0], line: 5 }; + gEditor.on("breakpointAdded", onEditorBreakpointAddBackgroundTrap); + return actions.addBreakpoint(location).then(() => location); + } + + function onEditorBreakpointAddBackgroundTrap() { + // Trap listener: no breakpoint must be added to the editor when a + // breakpoint is added to a source that is not currently selected. + editorBreakpointChanges++; + ok(false, "breakpoint2 must not be added to the editor."); + } + + function checkBackgroundBreakpoint(location) { + breakpointsAdded++; + const bp = queries.getBreakpoint(getState(), location); + + ok(bp, + "breakpoint2 added, client received"); + is(bp.location.actor, gSources.values[0], + "breakpoint2 client url is correct."); + is(bp.location.line, 5, + "breakpoint2 client line is correct."); + + ok(queries.getBreakpoint(getState(), bp.location), + "breakpoint2 found in the list of added breakpoints."); + + is(queries.getBreakpoints(getState()).length, 1, + "The list of added breakpoints holds only one breakpoint."); + + is(queries.getSelectedSource(getState()).actor, gSources.values[1], + "The second source should be currently selected."); + + // Remove the trap listener. + gEditor.off("breakpointAdded", onEditorBreakpointAddBackgroundTrap); + } + + function onEditorBreakpointAddSwitch(aEvent, aLine) { + editorBreakpointChanges++; + + ok(aEvent, + "breakpoint2 added to the editor."); + is(aLine, 4, + "Editor breakpoint line is correct."); + + is(gEditor.getBreakpoints().length, 1, + "editor.getBreakpoints().length is correct"); + } + + function onEditorTextChanged() { + // Wait for the actual text to be shown. + if (gEditor.getText() == gDebugger.L10N.getStr("loadingText")) + return void gEditor.once("change", onEditorTextChanged); + + is(gEditor.getText().indexOf("debugger"), -1, + "The second source is no longer displayed."); + is(gEditor.getText().indexOf("firstCall"), 118, + "The first source is displayed."); + + is(gSources.values[0], gSources.selectedValue, + "The first source should be currently selected."); + } + + function onReadyForClick() { + info("Remove the second breakpoint using the mouse."); + gEditor.once("breakpointRemoved", onEditorBreakpointRemoveSecond); + + let iframe = gEditor.container; + let testWin = iframe.ownerDocument.defaultView; + + // Flush the layout for the iframe. + info("rect " + iframe.contentDocument.documentElement.getBoundingClientRect()); + + let utils = testWin + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindowUtils); + + let coords = gEditor.getCoordsFromPosition({ line: 4, ch: 0 }); + let rect = iframe.getBoundingClientRect(); + let left = rect.left + 10; + let top = rect.top + coords.top + 4; + utils.sendMouseEventToWindow("mousedown", left, top, 0, 1, 0, false, 0, 0); + utils.sendMouseEventToWindow("mouseup", left, top, 0, 1, 0, false, 0, 0); + } + + function onEditorBreakpointRemoveSecond(aEvent, aLine) { + editorBreakpointChanges++; + + ok(aEvent, + "breakpoint2 removed from the editor."); + is(aLine, 4, + "Editor breakpoint line is correct."); + + is(gEditor.getBreakpoints().length, 0, + "editor.getBreakpoints().length is correct."); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => { + finalCheck(); + closeDebuggerAndFinish(gPanel); + }); + + gDebugger.gThreadClient.resume(); + } + + function finalCheck() { + is(queries.getBreakpoints(getState()).length, 0, + "No breakpoints currently added."); + + is(breakpointsAdded, 2, + "Correct number of breakpoints have been added."); + is(breakpointsRemoved, 1, + "Correct number of breakpoints have been removed."); + is(editorBreakpointChanges, 4, + "Correct number of editor breakpoint changes."); + } + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-eval.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-eval.js new file mode 100644 index 000000000..795059c70 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-eval.js @@ -0,0 +1,47 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test setting breakpoints on an eval script + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-eval.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-eval.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + const actions = bindActionCreators(gPanel); + + Task.spawn(function* () { + let newSource = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.NEW_SOURCE); + callInTab(gTab, "evalSourceWithSourceURL"); + yield newSource; + // Wait for it to be added to the UI + yield waitForTick(); + + const newSourceActor = getSourceActor(gSources, EXAMPLE_URL + "bar.js"); + yield actions.addBreakpoint({ + actor: newSourceActor, + line: 2 + }); + yield ensureThreadClientState(gPanel, "resumed"); + + const paused = waitForThreadEvents(gPanel, "paused"); + callInTab(gTab, "bar"); + let frame = (yield paused).frame; + is(frame.where.source.actor, newSourceActor, "Should have broken on the eval'ed source"); + is(frame.where.line, 2, "Should break on line 2"); + + yield resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-highlight.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-highlight.js new file mode 100644 index 000000000..d04a752ca --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-highlight.js @@ -0,0 +1,90 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test if breakpoints are highlighted when they should. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + const addBreakpoints = Task.async(function* () { + yield actions.addBreakpoint({ actor: gSources.values[0], line: 5 }); + yield actions.addBreakpoint({ actor: gSources.values[1], line: 6 }); + yield actions.addBreakpoint({ actor: gSources.values[1], line: 7 }); + yield actions.addBreakpoint({ actor: gSources.values[1], line: 8 }); + yield actions.addBreakpoint({ actor: gSources.values[1], line: 9 }); + }); + + function clickBreakpointAndCheck(aBreakpointIndex, aSourceIndex, aCaretLine) { + let finished = waitForCaretUpdated(gPanel, aCaretLine).then(() => { + checkHighlight(gSources.values[aSourceIndex], aCaretLine); + checkEditorContents(aSourceIndex); + + is(queries.getSelectedSource(getState()).actor, + gSources.items[aSourceIndex].value, + "The currently selected source value is incorrect (1)."); + ok(isCaretPos(gPanel, aCaretLine), + "The editor caret line and column were incorrect (1)."); + }); + + EventUtils.sendMouseEvent( + { type: "click" }, + gDebugger.document.querySelectorAll(".dbg-breakpoint")[aBreakpointIndex], + gDebugger + ); + + return finished; + } + + function checkHighlight(actor, line) { + let breakpoint = gSources._selectedBreakpoint; + let breakpointItem = gSources._getBreakpoint(breakpoint); + + is(breakpoint.location.actor, actor, + "The currently selected breakpoint actor is incorrect."); + is(breakpoint.location.line, line, + "The currently selected breakpoint line is incorrect."); + is(breakpointItem.attachment.actor, actor, + "The selected breakpoint item's source location attachment is incorrect."); + ok(breakpointItem.target.classList.contains("selected"), + "The selected breakpoint item's target should have a selected class."); + } + + function checkEditorContents(aSourceIndex) { + if (aSourceIndex == 0) { + is(gEditor.getText().indexOf("firstCall"), 118, + "The first source is correctly displayed."); + } else { + is(gEditor.getText().indexOf("debugger"), 166, + "The second source is correctly displayed."); + } + } + + Task.spawn(function* () { + yield addBreakpoints(); + yield clickBreakpointAndCheck(0, 0, 5); + yield clickBreakpointAndCheck(1, 1, 6); + yield clickBreakpointAndCheck(2, 1, 7); + yield clickBreakpointAndCheck(3, 1, 8); + yield clickBreakpointAndCheck(4, 1, 9); + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-new-script.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-new-script.js new file mode 100644 index 000000000..6e0537f82 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-new-script.js @@ -0,0 +1,92 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 771452: Make sure that setting a breakpoint in an inline source doesn't + * add it twice. + */ + +const TAB_URL = EXAMPLE_URL + "doc_inline-script.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require('./content/queries'); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + function testResume() { + const deferred = promise.defer(); + is(gDebugger.gThreadClient.state, "paused", + "The breakpoint wasn't hit yet."); + + gDebugger.gThreadClient.resume(() => { + gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => { + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => { + is(aPacket.why.type, "breakpoint", + "Execution has advanced to the next breakpoint."); + isnot(aPacket.why.type, "debuggerStatement", + "The breakpoint was hit before the debugger statement."); + ok(isCaretPos(gPanel, 20), + "The source editor caret position is incorrect (2)."); + + deferred.resolve(); + }); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); + + return deferred.promise; + } + + function testBreakpointHit() { + const deferred = promise.defer(); + is(gDebugger.gThreadClient.state, "paused", + "The breakpoint was hit."); + + gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => { + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => { + is(aPacket.why.type, "debuggerStatement", + "Execution has advanced to the next line."); + isnot(aPacket.why.type, "breakpoint", + "No ghost breakpoint was hit."); + ok(isCaretPos(gPanel, 20), + "The source editor caret position is incorrect (3)."); + + deferred.resolve(); + }); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + return deferred.promise; + } + + Task.spawn(function(){ + let onCaretUpdated = waitForCaretAndScopes(gPanel, 16); + callInTab(gTab, "runDebuggerStatement"); + yield onCaretUpdated; + + is(gDebugger.gThreadClient.state, "paused", + "The debugger statement was reached."); + ok(isCaretPos(gPanel, 16), + "The source editor caret position is incorrect (1)."); + + yield actions.addBreakpoint({ actor: getSourceActor(gSources, TAB_URL), line: 20 }); + yield testResume(); + yield testBreakpointHit(); + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-other-tabs.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-other-tabs.js new file mode 100644 index 000000000..2763eee95 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-other-tabs.js @@ -0,0 +1,41 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that setting a breakpoint in one tab, doesn't cause another tab at + * the same source to pause at that location. + */ + +const TAB_URL = EXAMPLE_URL + "doc_breakpoints-other-tabs.html"; + +var test = Task.async(function* () { + const options = { + source: EXAMPLE_URL + "code_breakpoints-other-tabs.js", + line: 1 + }; + const [tab1,, panel1] = yield initDebugger(TAB_URL, options); + const [tab2,, panel2] = yield initDebugger(TAB_URL, options); + const queries = panel1.panelWin.require("./content/queries"); + const actions = bindActionCreators(panel1); + const getState = panel1.panelWin.DebuggerController.getState; + + const sources = panel1.panelWin.DebuggerView.Sources; + + yield actions.addBreakpoint({ + actor: queries.getSelectedSource(getState()).actor, + line: 2 + }); + + const paused = waitForThreadEvents(panel2, "paused"); + callInTab(tab2, "testCase"); + const packet = yield paused; + + is(packet.why.type, "debuggerStatement", + "Should have stopped at the debugger statement, not the other tab's breakpoint"); + is(packet.frame.where.line, 3, + "Should have stopped at line 3 (debugger statement), not line 2 (other tab's breakpoint)"); + + yield resumeDebuggerThenCloseAndFinish(panel2); +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-pane.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-pane.js new file mode 100644 index 000000000..c576603d4 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-pane.js @@ -0,0 +1,238 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 723071: Test adding a pane to display the list of breakpoints across + * all sources in the debuggee. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + const { getBreakpoint } = queries; + + let breakpointsAdded = 0; + let breakpointsDisabled = 0; + let breakpointsRemoved = 0; + let breakpointsList; + + const addBreakpoints = Task.async(function* (aIncrementFlag) { + const loc1 = { actor: gSources.selectedValue, line: 6 }; + yield actions.addBreakpoint(loc1); + onBreakpointAdd(getBreakpoint(getState(), loc1), { + increment: aIncrementFlag, + line: 6, + text: "debugger;" + }); + + const loc2 = { actor: gSources.selectedValue, line: 7 }; + yield actions.addBreakpoint(loc2); + onBreakpointAdd(getBreakpoint(getState(), loc2), { + increment: aIncrementFlag, + line: 7, + text: "function foo() {}" + }); + + const loc3 = {actor: gSources.selectedValue, line: 9 }; + yield actions.addBreakpoint(loc3); + onBreakpointAdd(getBreakpoint(getState(), loc3), { + increment: aIncrementFlag, + line: 9, + text: "foo();" + }); + }); + + function disableBreakpoints() { + let deferred = promise.defer(); + + let nodes = breakpointsList.querySelectorAll(".dbg-breakpoint"); + info("Nodes to disable: " + breakpointsAdded.length); + + is(nodes.length, breakpointsAdded, + "The number of nodes to disable is incorrect."); + + for (let node of nodes) { + info("Disabling breakpoint: " + node.id); + + let sourceItem = gSources.getItemForElement(node); + let breakpointItem = gSources.getItemForElement.call(sourceItem, node); + info("Found data: " + breakpointItem.attachment.toSource()); + + actions.disableBreakpoint(breakpointItem.attachment).then(() => { + if (++breakpointsDisabled == breakpointsAdded) { + deferred.resolve(); + } + }); + } + + return deferred.promise; + } + + function removeBreakpoints() { + let deferred = promise.defer(); + + let nodes = breakpointsList.querySelectorAll(".dbg-breakpoint"); + info("Nodes to remove: " + breakpointsAdded.length); + + is(nodes.length, breakpointsAdded, + "The number of nodes to remove is incorrect."); + + for (let node of nodes) { + info("Removing breakpoint: " + node.id); + + let sourceItem = gSources.getItemForElement(node); + let breakpointItem = gSources.getItemForElement.call(sourceItem, node); + info("Found data: " + breakpointItem.attachment.toSource()); + + actions.removeBreakpoint(breakpointItem.attachment).then(() => { + if (++breakpointsRemoved == breakpointsAdded) { + deferred.resolve(); + } + }); + } + + return deferred.promise; + } + + function onBreakpointAdd(bp, testData) { + if (testData.increment) { + breakpointsAdded++; + } + + is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsAdded, + testData.increment + ? "Should have added a breakpoint in the pane." + : "Should have the same number of breakpoints in the pane."); + + let identifier = queries.makeLocationId(bp.location); + let node = gDebugger.document.getElementById("breakpoint-" + identifier); + let line = node.getElementsByClassName("dbg-breakpoint-line")[0]; + let text = node.getElementsByClassName("dbg-breakpoint-text")[0]; + let check = node.querySelector("checkbox"); + + ok(node, + "Breakpoint element found successfully."); + is(line.getAttribute("value"), testData.line, + "The expected information wasn't found in the breakpoint element."); + is(text.getAttribute("value"), testData.text, + "The expected line text wasn't found in the breakpoint element."); + is(check.getAttribute("checked"), "true", + "The breakpoint enable checkbox is checked as expected."); + } + + Task.spawn(function* () { + yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1); + + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(queries.getSourceCount(getState()), 2, + "Found the expected number of sources."); + is(gEditor.getText().indexOf("debugger"), 166, + "The correct source was loaded initially."); + is(gSources.selectedValue, gSources.values[1], + "The correct source is selected."); + + is(queries.getBreakpoints(getState()).length, 0, + "No breakpoints currently added."); + + let breakpointsParent = gSources.widget._parent; + breakpointsList = gSources.widget._list; + + is(breakpointsParent.childNodes.length, 1, // one sources list + "Found junk in the breakpoints container."); + is(breakpointsList.childNodes.length, 1, // one sources group + "Found junk in the breakpoints container."); + is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, 0, + "No breakpoints should be visible at this point."); + + yield addBreakpoints(true); + + is(breakpointsAdded, 3, + "Should have added 3 breakpoints so far."); + is(breakpointsDisabled, 0, + "Shouldn't have disabled anything so far."); + is(breakpointsRemoved, 0, + "Shouldn't have removed anything so far."); + + is(breakpointsParent.childNodes.length, 1, // one sources list + "Found junk in the breakpoints container."); + is(breakpointsList.childNodes.length, 1, // one sources group + "Found junk in the breakpoints container."); + is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, 3, + "3 breakpoints should be visible at this point."); + + yield disableBreakpoints(); + + is(breakpointsAdded, 3, + "Should still have 3 breakpoints added so far."); + is(breakpointsDisabled, 3, + "Should have 3 disabled breakpoints."); + is(breakpointsRemoved, 0, + "Shouldn't have removed anything so far."); + + is(breakpointsParent.childNodes.length, 1, // one sources list + "Found junk in the breakpoints container."); + is(breakpointsList.childNodes.length, 1, // one sources group + "Found junk in the breakpoints container."); + is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsAdded, + "Should have the same number of breakpoints in the pane."); + is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsDisabled, + "Should have the same number of disabled breakpoints."); + + yield addBreakpoints(); + + is(breakpointsAdded, 3, + "Should still have only 3 breakpoints added so far."); + is(breakpointsDisabled, 3, + "Should still have 3 disabled breakpoints."); + is(breakpointsRemoved, 0, + "Shouldn't have removed anything so far."); + + is(breakpointsParent.childNodes.length, 1, // one sources list + "Found junk in the breakpoints container."); + is(breakpointsList.childNodes.length, 1, // one sources group + "Found junk in the breakpoints container."); + is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, breakpointsAdded, + "Since half of the breakpoints already existed, but disabled, " + + "only half of the added breakpoints are actually in the pane."); + + yield removeBreakpoints(); + + is(breakpointsRemoved, 3, + "Should have 3 removed breakpoints."); + + is(breakpointsParent.childNodes.length, 1, // one sources list + "Found junk in the breakpoints container."); + is(breakpointsList.childNodes.length, 1, // one sources group + "Found junk in the breakpoints container."); + is(breakpointsList.querySelectorAll(".dbg-breakpoint").length, 0, + "No breakpoints should be visible at this point."); + + const cleared = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED); + gDebugger.gThreadClient.resume(); + yield cleared; + + is(queries.getBreakpoints(getState()).length, 0, + "No breakpoints currently added."); + + closeDebuggerAndFinish(gPanel); + }); + + callInTab(gTab, "firstCall"); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-reload.js b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-reload.js new file mode 100644 index 000000000..b86c0ec04 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_breakpoints-reload.js @@ -0,0 +1,39 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that setting a breakpoint on code that gets run on load, will get + * hit when we reload. + */ + +const TAB_URL = EXAMPLE_URL + "doc_breakpoints-reload.html"; + +var test = Task.async(function* () { + requestLongerTimeout(4); + + const options = { + source: TAB_URL, + line: 1 + }; + const [tab,, panel] = yield initDebugger(TAB_URL, options); + const actions = bindActionCreators(panel); + + const sources = panel.panelWin.DebuggerView.Sources; + yield actions.addBreakpoint({ + actor: sources.selectedValue, + line: 10 // "break on me" string + }); + + const paused = waitForThreadEvents(panel, "paused"); + yield reloadActiveTab(panel, panel.panelWin.EVENTS.SOURCE_SHOWN); + const packet = yield paused; + + is(packet.why.type, "breakpoint", + "Should have hit the breakpoint after the reload"); + is(packet.frame.where.line, 10, + "Should have stopped at line 10, where we set the breakpoint"); + + yield resumeDebuggerThenCloseAndFinish(panel); +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_bug-896139.js b/devtools/client/debugger/test/mochitest/browser_dbg_bug-896139.js new file mode 100644 index 000000000..39df159bc --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_bug-896139.js @@ -0,0 +1,48 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 896139 - Breakpoints not triggering when reloading script. + */ + +const TAB_URL = EXAMPLE_URL + "doc_bug-896139.html"; +const SCRIPT_URL = EXAMPLE_URL + "code_bug-896139.js"; + +function test() { + Task.spawn(function* () { + function testBreakpoint() { + let promise = waitForDebuggerEvents(panel, win.EVENTS.FETCHED_SCOPES); + callInTab(tab, "f"); + return promise.then(() => doResume(panel)); + } + + let options = { + source: SCRIPT_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + + let Sources = win.DebuggerView.Sources; + + yield panel.addBreakpoint({ + actor: getSourceActor(win.DebuggerView.Sources, SCRIPT_URL), + line: 6 + }); + + // Race condition: the setBreakpoint request sometimes leaves the + // debugger in paused state for a bit because we are called before + // that request finishes (see bug 1156531 for plans to fix) + if (panel.panelWin.gThreadClient.state !== "attached") { + yield waitForThreadEvents(panel, "resumed"); + } + + yield testBreakpoint(); + yield reloadActiveTab(panel, win.EVENTS.SOURCE_SHOWN); + yield testBreakpoint(); + + yield closeDebuggerAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_chrome-create.js b/devtools/client/debugger/test/mochitest/browser_dbg_chrome-create.js new file mode 100644 index 000000000..33c21c3d5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_chrome-create.js @@ -0,0 +1,64 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that a chrome debugger can be created in a new process. + */ + +var gProcess; + +function test() { + // Windows XP and 8.1 test slaves are terribly slow at this test. + requestLongerTimeout(5); + Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true); + + initChromeDebugger(aOnClose).then(aProcess => { + gProcess = aProcess; + + info("Starting test..."); + performTest(); + }); +} + +function performTest() { + ok(gProcess._dbgProcess, + "The remote debugger process wasn't created properly!"); + ok(gProcess._dbgProcess.isRunning, + "The remote debugger process isn't running!"); + is(typeof gProcess._dbgProcess.pid, "number", + "The remote debugger process doesn't have a pid (?!)"); + + info("process location: " + gProcess._dbgProcess.location); + info("process pid: " + gProcess._dbgProcess.pid); + info("process name: " + gProcess._dbgProcess.processName); + info("process sig: " + gProcess._dbgProcess.processSignature); + + ok(gProcess._dbgProfilePath, + "The remote debugger profile wasn't created properly!"); + is(gProcess._dbgProfilePath, OS.Path.join(OS.Constants.Path.profileDir, "chrome_debugger_profile"), + "The remote debugger profile isn't where we expect it!"); + + info("profile path: " + gProcess._dbgProfilePath); + + gProcess.close(); +} + +function aOnClose() { + ok(!gProcess._dbgProcess.isRunning, + "The remote debugger process isn't closed as it should be!"); + is(gProcess._dbgProcess.exitValue, (Services.appinfo.OS == "WINNT" ? 0 : 256), + "The remote debugger process didn't die cleanly."); + + info("process exit value: " + gProcess._dbgProcess.exitValue); + + info("profile path: " + gProcess._dbgProfilePath); + + finish(); +} + +registerCleanupFunction(function () { + Services.prefs.clearUserPref("devtools.debugger.remote-enabled"); + gProcess = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js b/devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js new file mode 100644 index 000000000..79e9e6566 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js @@ -0,0 +1,102 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that chrome debugging works. + */ + +const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html"; + +var gClient, gThreadClient; +var gAttached = promise.defer(); +var gNewGlobal = promise.defer(); +var gNewChromeSource = promise.defer(); + +var { DevToolsLoader } = Cu.import("resource://devtools/shared/Loader.jsm", {}); +var customLoader = new DevToolsLoader(); +customLoader.invisibleToDebugger = true; +var { DebuggerServer } = customLoader.require("devtools/server/main"); + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + DebuggerServer.allowChromeProcess = true; + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + promise.all([gAttached.promise, gNewGlobal.promise, gNewChromeSource.promise]) + .then(resumeAndCloseConnection) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + testChromeActor(); + }); +} + +function testChromeActor() { + gClient.getProcess().then(aResponse => { + gClient.addListener("newGlobal", onNewGlobal); + + let actor = aResponse.form.actor; + gClient.attachTab(actor, (response, tabClient) => { + tabClient.attachThread(null, (aResponse, aThreadClient) => { + gThreadClient = aThreadClient; + gThreadClient.addListener("newSource", onNewSource); + + if (aResponse.error) { + ok(false, "Couldn't attach to the chrome debugger."); + gAttached.reject(); + } else { + ok(true, "Attached to the chrome debugger."); + gAttached.resolve(); + + // Ensure that a new chrome global will be created. + gBrowser.selectedTab = gBrowser.addTab("about:mozilla"); + } + }); + }); + }); +} + +function onNewGlobal() { + ok(true, "Received a new chrome global."); + + gClient.removeListener("newGlobal", onNewGlobal); + gNewGlobal.resolve(); +} + +function onNewSource(aEvent, aPacket) { + if (aPacket.source.url.startsWith("chrome:")) { + ok(true, "Received a new chrome source: " + aPacket.source.url); + + gThreadClient.removeListener("newSource", onNewSource); + gNewChromeSource.resolve(); + } +} + +function resumeAndCloseConnection() { + let deferred = promise.defer(); + gThreadClient.resume(() => deferred.resolve(gClient.close())); + return deferred.promise; +} + +registerCleanupFunction(function () { + gClient = null; + gThreadClient = null; + gAttached = null; + gNewGlobal = null; + gNewChromeSource = null; + + customLoader = null; + DebuggerServer = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_clean-exit-window.js b/devtools/client/debugger/test/mochitest/browser_dbg_clean-exit-window.js new file mode 100644 index 000000000..d09e8c70c --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_clean-exit-window.js @@ -0,0 +1,86 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that closing a window with the debugger in a paused state exits cleanly. + */ + +var gDebuggee, gPanel, gDebugger, gWindow; + +const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html"; + +function test() { + addWindow(TAB_URL) + .then(win => initDebugger(TAB_URL, { window: win })) + .then(([aTab, aDebuggee, aPanel, aWindow]) => { + gDebuggee = aDebuggee; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gWindow = aWindow; + + return testCleanExit(); + }) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); +} + +function testCleanExit() { + let deferred = promise.defer(); + + ok(!!gWindow, "Second window created."); + + gWindow.focus(); + + is(Services.wm.getMostRecentWindow("navigator:browser"), gWindow, + "The second window is on top."); + + let isActive = promise.defer(); + let isLoaded = promise.defer(); + + promise.all([isActive.promise, isLoaded.promise]).then(() => { + waitForSourceAndCaretAndScopes(gPanel, ".html", 16).then(() => { + is(gDebugger.gThreadClient.paused, true, + "Should be paused after the debugger statement."); + gWindow.close(); + deferred.resolve(); + finish(); + }); + + gDebuggee.runDebuggerStatement(); + }); + + if (Services.focus.activeWindow != gWindow) { + gWindow.addEventListener("activate", function onActivate(aEvent) { + if (aEvent.target != gWindow) { + return; + } + gWindow.removeEventListener("activate", onActivate, true); + isActive.resolve(); + }, true); + } else { + isActive.resolve(); + } + + if (gWindow.content.location.href != TAB_URL) { + gWindow.document.addEventListener("load", function onLoad(aEvent) { + if (aEvent.target.documentURI != TAB_URL) { + return; + } + gWindow.document.removeEventListener("load", onLoad, true); + isLoaded.resolve(); + }, true); + } else { + isLoaded.resolve(); + } + return deferred.promise; +} + +registerCleanupFunction(function () { + gWindow = null; + gDebuggee = null; + gPanel = null; + gDebugger = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_clean-exit.js b/devtools/client/debugger/test/mochitest/browser_dbg_clean-exit.js new file mode 100644 index 000000000..25cbf550d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_clean-exit.js @@ -0,0 +1,44 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that closing a tab with the debugger in a paused state exits cleanly. + */ + +var gTab, gPanel, gDebugger; + +const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html"; + +function test() { + const options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + + testCleanExit(); + }); +} + +function testCleanExit() { + promise.all([ + waitForSourceAndCaretAndScopes(gPanel, ".html", 16), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED) + ]).then(() => { + is(gDebugger.gThreadClient.paused, true, + "Should be paused after the debugger statement."); + }).then(() => closeDebuggerAndFinish(gPanel, { whilePaused: true })); + + callInTab(gTab, "runDebuggerStatement"); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js b/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js new file mode 100644 index 000000000..739d3b2a7 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js @@ -0,0 +1,153 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TAB_URL = EXAMPLE_URL + "doc_closures.html"; + +// Test that inspecting a closure works as expected. + +function test() { + let gPanel, gTab, gDebugger; + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + + testClosure() + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); + + function testClosure() { + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + + return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => { + let gVars = gDebugger.DebuggerView.Variables; + let localScope = gVars.getScopeAtIndex(0); + let localNodes = localScope.target.querySelector(".variables-view-element-details").childNodes; + + is(localNodes[4].querySelector(".name").getAttribute("value"), "person", + "Should have the right property name for |person|."); + is(localNodes[4].querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for |person|."); + + // Expand the 'person' tree node. This causes its properties to be + // retrieved and displayed. + let personNode = gVars.getItemForNode(localNodes[4]); + let personFetched = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES); + personNode.expand(); + + return personFetched.then(() => { + is(personNode.expanded, true, + "|person| should be expanded at this point."); + + is(personNode.get("getName").target.querySelector(".name") + .getAttribute("value"), "getName", + "Should have the right property name for 'getName' in person."); + is(personNode.get("getName").target.querySelector(".value") + .getAttribute("value"), "_pfactory/<.getName()", + "'getName' in person should have the right value."); + is(personNode.get("getFoo").target.querySelector(".name") + .getAttribute("value"), "getFoo", + "Should have the right property name for 'getFoo' in person."); + is(personNode.get("getFoo").target.querySelector(".value") + .getAttribute("value"), "_pfactory/<.getFoo()", + "'getFoo' in person should have the right value."); + + // Expand the function nodes. This causes their properties to be + // retrieved and displayed. + let getFooNode = personNode.get("getFoo"); + let getNameNode = personNode.get("getName"); + let funcsFetched = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 2); + let funcClosuresFetched = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES, 2); + getFooNode.expand(); + getNameNode.expand(); + + return funcsFetched.then(() => { + is(getFooNode.expanded, true, + "|person.getFoo| should be expanded at this point."); + is(getNameNode.expanded, true, + "|person.getName| should be expanded at this point."); + + is(getFooNode.get("<Closure>").target.querySelector(".name") + .getAttribute("value"), "<Closure>", + "Found the closure node for getFoo."); + is(getFooNode.get("<Closure>").target.querySelector(".value") + .getAttribute("value"), "", + "The closure node has no value for getFoo."); + is(getNameNode.get("<Closure>").target.querySelector(".name") + .getAttribute("value"), "<Closure>", + "Found the closure node for getName."); + is(getNameNode.get("<Closure>").target.querySelector(".value") + .getAttribute("value"), "", + "The closure node has no value for getName."); + + // Expand the closure nodes. This causes their environments to be + // retrieved and displayed. + let getFooClosure = getFooNode.get("<Closure>"); + let getNameClosure = getNameNode.get("<Closure>"); + getFooClosure.expand(); + getNameClosure.expand(); + + return funcClosuresFetched.then(() => { + is(getFooClosure.expanded, true, + "|person.getFoo| closure should be expanded at this point."); + is(getNameClosure.expanded, true, + "|person.getName| closure should be expanded at this point."); + + is(getFooClosure.get("Function scope [_pfactory]").target.querySelector(".name") + .getAttribute("value"), "Function scope [_pfactory]", + "Found the function scope node for the getFoo closure."); + is(getFooClosure.get("Function scope [_pfactory]").target.querySelector(".value") + .getAttribute("value"), "", + "The function scope node has no value for the getFoo closure."); + is(getNameClosure.get("Function scope [_pfactory]").target.querySelector(".name") + .getAttribute("value"), "Function scope [_pfactory]", + "Found the function scope node for the getName closure."); + is(getNameClosure.get("Function scope [_pfactory]").target.querySelector(".value") + .getAttribute("value"), "", + "The function scope node has no value for the getName closure."); + + // Expand the scope nodes. + let getFooInnerScope = getFooClosure.get("Function scope [_pfactory]"); + let getNameInnerScope = getNameClosure.get("Function scope [_pfactory]"); + let innerFuncsFetched = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 2); + getFooInnerScope.expand(); + getNameInnerScope.expand(); + + return funcsFetched.then(() => { + is(getFooInnerScope.expanded, true, + "|person.getFoo| inner scope should be expanded at this point."); + is(getNameInnerScope.expanded, true, + "|person.getName| inner scope should be expanded at this point."); + + // Only test that each function closes over the necessary variable. + // We wouldn't want future SpiderMonkey closure space + // optimizations to break this test. + is(getFooInnerScope.get("foo").target.querySelector(".name") + .getAttribute("value"), "foo", + "Found the foo node for the getFoo inner scope."); + is(getFooInnerScope.get("foo").target.querySelector(".value") + .getAttribute("value"), "10", + "The foo node has the expected value."); + is(getNameInnerScope.get("name").target.querySelector(".name") + .getAttribute("value"), "name", + "Found the name node for the getName inner scope."); + is(getNameInnerScope.get("name").target.querySelector(".value") + .getAttribute("value"), '"Bob"', + "The name node has the expected value."); + }); + }); + }); + }); + }); + } +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_cmd-blackbox.js b/devtools/client/debugger/test/mochitest/browser_dbg_cmd-blackbox.js new file mode 100644 index 000000000..06ff8a4f3 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_cmd-blackbox.js @@ -0,0 +1,117 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the 'dbg blackbox' and 'dbg unblackbox' commands work as + * they should. + */ + +const TEST_URL = EXAMPLE_URL + "doc_blackboxing.html"; +const BLACKBOXME_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js"; +const BLACKBOXONE_URL = EXAMPLE_URL + "code_blackboxing_one.js"; +const BLACKBOXTWO_URL = EXAMPLE_URL + "code_blackboxing_two.js"; +const BLACKBOXTHREE_URL = EXAMPLE_URL + "code_blackboxing_three.js"; + +function test() { + return Task.spawn(spawnTest).then(finish, helpers.handleError); +} + +function* spawnTest() { + let options = yield helpers.openTab(TEST_URL); + yield helpers.openToolbar(options); + + let toolbox = yield gDevTools.showToolbox(options.target, "jsdebugger"); + let panel = toolbox.getCurrentPanel(); + let constants = panel.panelWin.require("./content/constants"); + + yield waitForDebuggerEvents(panel, panel.panelWin.EVENTS.SOURCE_SHOWN); + + function cmd(aTyped, aEventRepeat = 1, aOutput = "") { + return promise.all([ + waitForDispatch(panel, constants.BLACKBOX, aEventRepeat), + helpers.audit(options, [{ setup: aTyped, output: aOutput, exec: {} }]) + ]); + } + + // test Black-Box Source + yield cmd("dbg blackbox " + BLACKBOXME_URL); + + let bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXME_URL); + ok(bbButton.checked, + "Should be able to black box a specific source."); + + // test Un-Black-Box Source + yield cmd("dbg unblackbox " + BLACKBOXME_URL); + + bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXME_URL); + ok(!bbButton.checked, + "Should be able to stop black boxing a specific source."); + + // test Black-Box Glob + yield cmd("dbg blackbox --glob *blackboxing_t*.js", 2, + [/blackboxing_three\.js/g, /blackboxing_two\.js/g]); + + bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXME_URL); + ok(!bbButton.checked, + "blackboxme should not be black boxed because it doesn't match the glob."); + bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXONE_URL); + ok(!bbButton.checked, + "blackbox_one should not be black boxed because it doesn't match the glob."); + + bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXTWO_URL); + ok(bbButton.checked, + "blackbox_two should be black boxed because it matches the glob."); + bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXTHREE_URL); + ok(bbButton.checked, + "blackbox_three should be black boxed because it matches the glob."); + + // test Un-Black-Box Glob + yield cmd("dbg unblackbox --glob *blackboxing_t*.js", 2); + + bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXTWO_URL); + ok(!bbButton.checked, + "blackbox_two should be un-black boxed because it matches the glob."); + bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXTHREE_URL); + ok(!bbButton.checked, + "blackbox_three should be un-black boxed because it matches the glob."); + + // test Black-Box Invert + yield cmd("dbg blackbox --invert --glob *blackboxing_t*.js", 3, + [/blackboxing_three\.js/g, /blackboxing_two\.js/g]); + + bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXME_URL); + ok(bbButton.checked, + "blackboxme should be black boxed because it doesn't match the glob."); + bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXONE_URL); + ok(bbButton.checked, + "blackbox_one should be black boxed because it doesn't match the glob."); + bbButton = yield selectSourceAndGetBlackBoxButton(panel, TEST_URL); + ok(bbButton.checked, + "TEST_URL should be black boxed because it doesn't match the glob."); + + bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXTWO_URL); + ok(!bbButton.checked, + "blackbox_two should not be black boxed because it matches the glob."); + bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXTHREE_URL); + ok(!bbButton.checked, + "blackbox_three should not be black boxed because it matches the glob."); + + // test Un-Black-Box Invert + yield cmd("dbg unblackbox --invert --glob *blackboxing_t*.js", 3); + + bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXME_URL); + ok(!bbButton.checked, + "blackboxme should be un-black boxed because it does not match the glob."); + bbButton = yield selectSourceAndGetBlackBoxButton(panel, BLACKBOXONE_URL); + ok(!bbButton.checked, + "blackbox_one should be un-black boxed because it does not match the glob."); + bbButton = yield selectSourceAndGetBlackBoxButton(panel, TEST_URL); + ok(!bbButton.checked, + "TEST_URL should be un-black boxed because it doesn't match the glob."); + + yield teardown(panel, { noTabRemoval: true }); + yield helpers.closeToolbar(options); + yield helpers.closeTab(options); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_cmd-break.js b/devtools/client/debugger/test/mochitest/browser_dbg_cmd-break.js new file mode 100644 index 000000000..121bc5e99 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_cmd-break.js @@ -0,0 +1,225 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the break commands works as they should. + */ + +const TAB_URL = EXAMPLE_URL + "doc_cmd-break.html"; +var TAB_URL_ACTOR; + +function test() { + let gPanel, gDebugger, gThreadClient, gSources; + let gLineNumber; + + let expectedActorObj = { + value: null, + message: "" + }; + + helpers.addTabWithToolbar(TAB_URL, aOptions => { + return Task.spawn(function* () { + yield helpers.audit(aOptions, [{ + setup: "break", + check: { + input: "break", + hints: " add line", + markup: "IIIII", + status: "ERROR", + } + }]); + + yield helpers.audit(aOptions, [{ + setup: "break add", + check: { + input: "break add", + hints: " line", + markup: "IIIIIVIII", + status: "ERROR" + } + }]); + + yield helpers.audit(aOptions, [{ + setup: "break add line", + check: { + input: "break add line", + hints: " <file> <line>", + markup: "VVVVVVVVVVVVVV", + status: "ERROR" + } + }]); + + yield helpers.audit(aOptions, [{ + name: "open toolbox", + setup: Task.async(function* () { + let [aTab, aDebuggee, aPanel] = yield initDebugger(gBrowser.selectedTab); + + // Spin the event loop before causing the debuggee to pause, to allow this + // function to return first. + executeSoon(() => aDebuggee.firstCall()); + + yield waitForSourceAndCaretAndScopes(aPanel, ".html", 1); + + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gThreadClient = gPanel.panelWin.gThreadClient; + gLineNumber = yield ContentTask.spawn(aOptions.browser, {}, function* () { + return "" + content.wrappedJSObject.gLineNumber; + }); + gSources = gDebugger.DebuggerView.Sources; + + expectedActorObj.value = getSourceActor(gSources, TAB_URL); + }), + post: function () { + ok(gThreadClient, "Debugger client exists."); + is(gLineNumber, 14, "gLineNumber is correct."); + }, + }]); + + yield helpers.audit(aOptions, [{ + name: "break add line .../doc_cmd-break.html 14", + setup: function () { + // We have to setup in a function to allow gLineNumber to be initialized. + let line = "break add line " + TAB_URL + " " + gLineNumber; + return helpers.setInput(aOptions, line); + }, + check: { + hints: "", + status: "VALID", + message: "", + args: { + file: expectedActorObj, + line: { value: 14 } + } + }, + exec: { + output: "Added breakpoint" + } + }]); + + yield helpers.audit(aOptions, [{ + setup: "break add line " + TAB_URL + " 17", + check: { + hints: "", + status: "VALID", + message: "", + args: { + file: expectedActorObj, + line: { value: 17 } + } + }, + exec: { + output: "Added breakpoint" + } + }]); + + yield helpers.audit(aOptions, [{ + setup: "break list", + check: { + input: "break list", + hints: "", + markup: "VVVVVVVVVV", + status: "VALID" + }, + exec: { + output: [ + /Source/, /Remove/, + /doc_cmd-break\.html:14/, + /doc_cmd-break\.html:17/ + ] + } + }]); + + yield helpers.audit(aOptions, [{ + name: "cleanup", + setup: function () { + let deferred = promise.defer(); + gThreadClient.resume(deferred.resolve); + return deferred.promise; + } + }]); + + yield helpers.audit(aOptions, [{ + setup: "break del 14", + check: { + input: "break del 14", + hints: " -> doc_cmd-break.html:14", + markup: "VVVVVVVVVVII", + status: "ERROR", + args: { + breakpoint: { + status: "INCOMPLETE", + message: "Value required for \u2018breakpoint\u2019." + } + } + } + }]); + + yield helpers.audit(aOptions, [{ + setup: "break del doc_cmd-break.html:14", + check: { + input: "break del doc_cmd-break.html:14", + hints: "", + markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV", + status: "VALID", + args: { + breakpoint: { arg: " doc_cmd-break.html:14" }, + } + }, + exec: { + output: "Breakpoint removed" + } + }]); + + yield helpers.audit(aOptions, [{ + setup: "break list", + check: { + input: "break list", + hints: "", + markup: "VVVVVVVVVV", + status: "VALID" + }, + exec: { + output: [ + /Source/, /Remove/, + /doc_cmd-break\.html:17/ + ] + } + }]); + + yield helpers.audit(aOptions, [{ + setup: "break del doc_cmd-break.html:17", + check: { + input: "break del doc_cmd-break.html:17", + hints: "", + markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV", + status: "VALID", + args: { + breakpoint: { arg: " doc_cmd-break.html:17" }, + } + }, + exec: { + output: "Breakpoint removed" + } + }]); + + yield helpers.audit(aOptions, [{ + setup: "break list", + check: { + input: "break list", + hints: "", + markup: "VVVVVVVVVV", + status: "VALID" + }, + exec: { + output: "No breakpoints set" + }, + post: function () { + return teardown(gPanel, { noTabRemoval: true }); + } + }]); + }); + }).then(finish); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_cmd-dbg.js b/devtools/client/debugger/test/mochitest/browser_dbg_cmd-dbg.js new file mode 100644 index 000000000..e843d0cd5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_cmd-dbg.js @@ -0,0 +1,102 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the debugger commands work as they should. + */ + +const TEST_URI = EXAMPLE_URL + "doc_cmd-dbg.html"; + +function test() { + return Task.spawn(function* () { + let options = yield helpers.openTab(TEST_URI); + yield helpers.openToolbar(options); + + yield helpers.audit(options, [{ + setup: "dbg open", + exec: { output: "" } + }]); + + let [gTab, gDebuggee, gPanel] = yield initDebugger(gBrowser.selectedTab); + let gDebugger = gPanel.panelWin; + let gThreadClient = gDebugger.gThreadClient; + + yield helpers.audit(options, [{ + setup: "dbg list", + exec: { output: /doc_cmd-dbg.html/ } + }]); + + let button = gDebuggee.document.querySelector("input[type=button]"); + let output = gDebuggee.document.querySelector("input[type=text]"); + + let cmd = function (aTyped, aState) { + return promise.all([ + waitForThreadEvents(gPanel, aState), + helpers.audit(options, [{ setup: aTyped, exec: { output: "" } }]) + ]); + }; + + let click = function (aElement, aState) { + return promise.all([ + waitForThreadEvents(gPanel, aState), + executeSoon(() => EventUtils.sendMouseEvent({ type: "click" }, aElement, gDebuggee)) + ]); + }; + + yield cmd("dbg interrupt", "paused"); + is(gThreadClient.state, "paused", "Debugger is paused."); + + yield cmd("dbg continue", "resumed"); + isnot(gThreadClient.state, "paused", "Debugger has continued."); + + yield click(button, "paused"); + is(gThreadClient.state, "paused", "Debugger is paused again."); + + yield cmd("dbg step in", "paused"); + yield cmd("dbg step in", "paused"); + yield cmd("dbg step in", "paused"); + is(output.value, "step in", "Debugger stepped in."); + + yield cmd("dbg step over", "paused"); + is(output.value, "step over", "Debugger stepped over."); + + yield cmd("dbg step out", "paused"); + is(output.value, "step out", "Debugger stepped out."); + + yield cmd("dbg continue", "paused"); + is(output.value, "dbg continue", "Debugger continued."); + + let closeDebugger = function () { + let deferred = promise.defer(); + + helpers.audit(options, [{ + setup: "dbg close", + exec: { output: "" } + }]) + .then(() => { + let toolbox = gDevTools.getToolbox(options.target); + if (!toolbox) { + ok(true, "Debugger is closed."); + deferred.resolve(); + } else { + toolbox.on("destroyed", () => { + ok(true, "Debugger just closed."); + deferred.resolve(); + }); + } + }); + + return deferred.promise; + }; + + // We close the debugger twice to ensure 'dbg close' doesn't error when + // toolbox is already closed. See bug 884638 for more info. + yield closeDebugger(); + yield closeDebugger(); + yield helpers.closeToolbar(options); + yield helpers.closeTab(options); + + }).then(finish, helpers.handleError); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-01.js new file mode 100644 index 000000000..d8f9fca04 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-01.js @@ -0,0 +1,218 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 740825: Test the debugger conditional breakpoints. + */ + +const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html"; + +function test() { + // Linux debug test slaves are a bit slow at this test sometimes. + requestLongerTimeout(2); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + // This test forces conditional breakpoints to be evaluated on the + // client-side + var client = gPanel.target.client; + client.mainRoot.traits.conditionalBreakpoints = false; + + const addBreakpoints = Task.async(function* () { + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 18 }, + "undefined"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 19 }, + "null"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 20 }, + "42"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 21 }, + "true"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 22 }, + "'nasu'"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 23 }, + "/regexp/"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 24 }, + "({})"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 25 }, + "(function() {})"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 26 }, + "(function() { return false; })()"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 27 }, + "a"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 28 }, + "a !== undefined"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 29 }, + "b"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 30 }, + "a !== null"); + }); + + function resumeAndTestBreakpoint(line) { + let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line)); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + + return finished; + } + + function resumeAndTestNoBreakpoint() { + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => { + is(gSources.itemCount, 1, + "Found the expected number of sources."); + is(gEditor.getText().indexOf("ermahgerd"), 253, + "The correct source was loaded initially."); + is(gSources.selectedValue, gSources.values[0], + "The correct source is selected."); + + ok(gSources.selectedItem, + "There should be a selected source in the sources pane."); + ok(!gSources._selectedBreakpoint, + "There should be no selected breakpoint in the sources pane."); + is(gSources._conditionalPopupVisible, false, + "The breakpoint conditional expression popup should not be shown."); + + is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 0, + "There should be no visible stackframes."); + is(gDebugger.document.querySelectorAll(".dbg-breakpoint").length, 13, + "There should be thirteen visible breakpoints."); + }); + + gDebugger.gThreadClient.resume(); + + return finished; + } + + function testBreakpoint(line, highlightBreakpoint) { + // Highlight the breakpoint only if required. + if (highlightBreakpoint) { + let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line)); + gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: line }); + return finished; + } + + let selectedActor = gSources.selectedValue; + let selectedBreakpoint = gSources._selectedBreakpoint; + let selectedBreakpointItem = gSources._getBreakpoint(selectedBreakpoint); + + ok(selectedActor, + "There should be a selected item in the sources pane."); + ok(selectedBreakpoint, + "There should be a selected breakpoint in the sources pane."); + + let source = gSources.selectedItem.attachment.source; + let bp = queries.getBreakpoint(getState(), selectedBreakpoint.location); + + ok(bp, "The selected breakpoint exists"); + is(bp.location.actor, source.actor, + "The breakpoint on line " + line + " wasn't added on the correct source."); + is(bp.location.line, line, + "The breakpoint on line " + line + " wasn't found."); + is(!!bp.disabled, false, + "The breakpoint on line " + line + " should be enabled."); + is(!!selectedBreakpointItem.attachment.openPopup, false, + "The breakpoint on line " + line + " should not have opened a popup."); + is(gSources._conditionalPopupVisible, false, + "The breakpoint conditional expression popup should not have been shown."); + isnot(bp.condition, undefined, + "The breakpoint on line " + line + " should have a conditional expression."); + ok(isCaretPos(gPanel, line), + "The editor caret position is not properly set."); + } + + const testAfterReload = Task.async(function* () { + let selectedActor = gSources.selectedValue; + let selectedBreakpoint = gSources._selectedBreakpoint; + + ok(selectedActor, + "There should be a selected item in the sources pane after reload."); + ok(!selectedBreakpoint, + "There should be no selected breakpoint in the sources pane after reload."); + + yield testBreakpoint(18, true); + yield testBreakpoint(19, true); + yield testBreakpoint(20, true); + yield testBreakpoint(21, true); + yield testBreakpoint(22, true); + yield testBreakpoint(23, true); + yield testBreakpoint(24, true); + yield testBreakpoint(25, true); + yield testBreakpoint(26, true); + yield testBreakpoint(27, true); + yield testBreakpoint(28, true); + yield testBreakpoint(29, true); + yield testBreakpoint(30, true); + + is(gSources.itemCount, 1, + "Found the expected number of sources."); + is(gEditor.getText().indexOf("ermahgerd"), 253, + "The correct source was loaded again."); + is(gSources.selectedValue, gSources.values[0], + "The correct source is selected."); + + ok(gSources.selectedItem, + "There should be a selected source in the sources pane."); + ok(gSources._selectedBreakpoint, + "There should be a selected breakpoint in the sources pane."); + is(gSources._conditionalPopupVisible, false, + "The breakpoint conditional expression popup should not be shown."); + }); + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretAndScopes(gPanel, 17); + callInTab(gTab, "ermahgerd"); + yield onCaretUpdated; + + yield addBreakpoints(); + + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(queries.getSourceCount(getState()), 1, + "Found the expected number of sources."); + is(gEditor.getText().indexOf("ermahgerd"), 253, + "The correct source was loaded initially."); + is(gSources.selectedValue, gSources.values[0], + "The correct source is selected."); + is(queries.getBreakpoints(getState()).length, 13, + "13 breakpoints currently added."); + + yield resumeAndTestBreakpoint(20); + yield resumeAndTestBreakpoint(21); + yield resumeAndTestBreakpoint(22); + yield resumeAndTestBreakpoint(23); + yield resumeAndTestBreakpoint(24); + yield resumeAndTestBreakpoint(25); + yield resumeAndTestBreakpoint(27); + yield resumeAndTestBreakpoint(28); + yield resumeAndTestBreakpoint(29); + yield resumeAndTestBreakpoint(30); + yield resumeAndTestNoBreakpoint(); + + let sourceShown = waitForSourceShown(gPanel, ".html"); + reload(gPanel), + yield sourceShown; + + testAfterReload(); + + // Reset traits back to default value + client.mainRoot.traits.conditionalBreakpoints = true; + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-02.js new file mode 100644 index 000000000..d3d857753 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-02.js @@ -0,0 +1,219 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 740825: Test the debugger conditional breakpoints. + */ + +const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + const CONDITIONAL_POPUP_SHOWN = gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWN; + + // This test forces conditional breakpoints to be evaluated on the + // client-side + var client = gPanel.target.client; + client.mainRoot.traits.conditionalBreakpoints = false; + + function addBreakpoint1() { + return actions.addBreakpoint({ actor: gSources.selectedValue, line: 18 }); + } + + function addBreakpoint2() { + let finished = waitForDispatch(gPanel, constants.ADD_BREAKPOINT); + setCaretPosition(19); + gSources._onCmdAddBreakpoint(); + return finished; + } + + function modBreakpoint2() { + setCaretPosition(19); + let popupShown = waitForDebuggerEvents(gPanel, CONDITIONAL_POPUP_SHOWN); + gSources._onCmdAddConditionalBreakpoint(); + return popupShown; + } + + function* addBreakpoint3() { + let finished = waitForDispatch(gPanel, constants.ADD_BREAKPOINT); + let popupShown = waitForDebuggerEvents(gPanel, CONDITIONAL_POPUP_SHOWN); + setCaretPosition(20); + gSources._onCmdAddConditionalBreakpoint(); + yield finished; + yield popupShown; + } + + function* modBreakpoint3() { + setCaretPosition(20); + + let popupShown = waitForDebuggerEvents(gPanel, CONDITIONAL_POPUP_SHOWN); + gSources._onCmdAddConditionalBreakpoint(); + yield popupShown; + + typeText(gSources._cbTextbox, "bamboocha"); + + let finished = waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION); + EventUtils.sendKey("RETURN", gDebugger); + yield finished; + } + + function addBreakpoint4() { + let finished = waitForDispatch(gPanel, constants.ADD_BREAKPOINT); + setCaretPosition(21); + gSources._onCmdAddBreakpoint(); + return finished; + } + + function delBreakpoint4() { + let finished = waitForDispatch(gPanel, constants.REMOVE_BREAKPOINT); + setCaretPosition(21); + gSources._onCmdAddBreakpoint(); + return finished; + } + + function testBreakpoint(aLine, aPopupVisible, aConditionalExpression) { + const source = queries.getSelectedSource(getState()); + ok(source, + "There should be a selected item in the sources pane."); + + const bp = queries.getBreakpoint(getState(), { + actor: source.actor, + line: aLine + }); + const bpItem = gSources._getBreakpoint(bp); + ok(bp, "There should be a breakpoint."); + ok(bpItem, "There should be a breakpoint in the sources pane."); + + is(bp.location.actor, source.actor, + "The breakpoint on line " + aLine + " wasn't added on the correct source."); + is(bp.location.line, aLine, + "The breakpoint on line " + aLine + " wasn't found."); + is(!!bp.disabled, false, + "The breakpoint on line " + aLine + " should be enabled."); + is(gSources._conditionalPopupVisible, aPopupVisible, + "The breakpoint on line " + aLine + " should have a correct popup state (2)."); + is(bp.condition, aConditionalExpression, + "The breakpoint on line " + aLine + " should have a correct conditional expression."); + } + + function testNoBreakpoint(aLine) { + let selectedActor = gSources.selectedValue; + let selectedBreakpoint = gSources._selectedBreakpoint; + + ok(selectedActor, + "There should be a selected item in the sources pane for line " + aLine + "."); + ok(!selectedBreakpoint, + "There should be no selected brekapoint in the sources pane for line " + aLine + "."); + + ok(isCaretPos(gPanel, aLine), + "The editor caret position is not properly set."); + } + + function setCaretPosition(aLine) { + gEditor.setCursor({ line: aLine - 1, ch: 0 }); + } + + function clickOnBreakpoint(aIndex) { + EventUtils.sendMouseEvent({ type: "click" }, + gDebugger.document.querySelectorAll(".dbg-breakpoint")[aIndex], + gDebugger); + } + + function waitForConditionUpdate() { + // This will close the popup and send another request to update + // the condition + gSources._hideConditionalPopup(); + return waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION); + } + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretAndScopes(gPanel, 17); + callInTab(gTab, "ermahgerd"); + yield onCaretUpdated; + + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(queries.getSourceCount(getState()), 1, + "Found the expected number of sources."); + is(gEditor.getText().indexOf("ermahgerd"), 253, + "The correct source was loaded initially."); + is(gSources.selectedValue, gSources.values[0], + "The correct source is selected."); + + is(queries.getBreakpoints(getState()).length, 0, + "No breakpoints currently added."); + + yield addBreakpoint1(); + testBreakpoint(18, false, undefined); + + yield addBreakpoint2(); + testBreakpoint(19, false, undefined); + yield modBreakpoint2(); + testBreakpoint(19, true, undefined); + yield waitForConditionUpdate(); + yield addBreakpoint3(); + testBreakpoint(20, true, ""); + yield waitForConditionUpdate(); + yield modBreakpoint3(); + testBreakpoint(20, false, "bamboocha"); + yield addBreakpoint4(); + testBreakpoint(21, false, undefined); + yield delBreakpoint4(); + + setCaretPosition(18); + is(gSources._selectedBreakpoint.location.line, 18, + "The selected breakpoint is line 18"); + yield testBreakpoint(18, false, undefined); + + setCaretPosition(19); + is(gSources._selectedBreakpoint.location.line, 19, + "The selected breakpoint is line 19"); + yield testBreakpoint(19, false, ""); + + setCaretPosition(20); + is(gSources._selectedBreakpoint.location.line, 20, + "The selected breakpoint is line 20"); + yield testBreakpoint(20, false, "bamboocha"); + + setCaretPosition(17); + yield testNoBreakpoint(17); + + setCaretPosition(21); + yield testNoBreakpoint(21); + + clickOnBreakpoint(0); + is(gSources._selectedBreakpoint.location.line, 18, + "The selected breakpoint is line 18"); + yield testBreakpoint(18, false, undefined); + + clickOnBreakpoint(1); + is(gSources._selectedBreakpoint.location.line, 19, + "The selected breakpoint is line 19"); + yield testBreakpoint(19, false, ""); + + clickOnBreakpoint(2); + is(gSources._selectedBreakpoint.location.line, 20, + "The selected breakpoint is line 20"); + testBreakpoint(20, true, "bamboocha"); + + // Reset traits back to default value + client.mainRoot.traits.conditionalBreakpoints = true; + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-03.js new file mode 100644 index 000000000..fbd9c6ae8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-03.js @@ -0,0 +1,78 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that conditional breakpoint expressions survive disabled breakpoints. + */ + +const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html"; + +function test() { + initDebugger().then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + // This test forces conditional breakpoints to be evaluated on the + // client-side + var client = gPanel.target.client; + client.mainRoot.traits.conditionalBreakpoints = false; + + function waitForConditionUpdate() { + // This will close the popup and send another request to update + // the condition + gSources._hideConditionalPopup(); + return waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION); + } + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretUpdated(gPanel, 17); + yield navigateActiveTabTo(gPanel, + TAB_URL, + gDebugger.EVENTS.SOURCE_SHOWN); + callInTab(gTab, "ermahgerd"); + yield onCaretUpdated; + + const location = { actor: gSources.selectedValue, line: 18 }; + + yield actions.addBreakpoint(location, "hello"); + yield actions.disableBreakpoint(location); + yield actions.addBreakpoint(location); + + const bp = queries.getBreakpoint(getState(), location); + is(bp.condition, "hello", "The conditional expression is correct."); + + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWN); + EventUtils.sendMouseEvent({ type: "click" }, + gDebugger.document.querySelector(".dbg-breakpoint"), + gDebugger); + yield finished; + + const textbox = gDebugger.document.getElementById("conditional-breakpoint-panel-textbox"); + is(textbox.value, "hello", "The expression is correct (2)."); + + yield waitForConditionUpdate(); + yield actions.disableBreakpoint(location); + yield actions.setBreakpointCondition(location, "foo"); + yield actions.addBreakpoint(location); + + finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWN); + EventUtils.sendMouseEvent({ type: "click" }, + gDebugger.document.querySelector(".dbg-breakpoint"), + gDebugger); + yield finished; + is(textbox.value, "foo", "The expression is correct (3)."); + + // Reset traits back to default value + client.mainRoot.traits.conditionalBreakpoints = true; + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-04.js new file mode 100644 index 000000000..55b217405 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-04.js @@ -0,0 +1,52 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that conditional breakpoints with blank expressions + * maintain their conditions after enabling them. + */ + +const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + // This test forces conditional breakpoints to be evaluated on the + // client-side + var client = gPanel.target.client; + client.mainRoot.traits.conditionalBreakpoints = false; + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretAndScopes(gPanel, 17); + callInTab(gTab, "ermahgerd"); + yield onCaretUpdated; + + const location = { actor: gSources.selectedValue, line: 18 }; + + yield actions.addBreakpoint(location, ""); + yield actions.disableBreakpoint(location); + yield actions.addBreakpoint(location); + + const bp = queries.getBreakpoint(getState(), location); + is(bp.condition, "", "The conditional expression is correct."); + + // Reset traits back to default value + client.mainRoot.traits.conditionalBreakpoints = true; + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-05.js new file mode 100644 index 000000000..f143ef535 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_conditional-breakpoints-05.js @@ -0,0 +1,141 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that conditional breakpoints with an exception-throwing expression + * could pause on hit + */ + +const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + // This test forces conditional breakpoints to be evaluated on the + // client-side + var client = gPanel.target.client; + client.mainRoot.traits.conditionalBreakpoints = false; + + function resumeAndTestBreakpoint(line) { + let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line)); + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + + return finished; + } + + function resumeAndTestNoBreakpoint() { + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => { + is(gSources.itemCount, 1, + "Found the expected number of sources."); + is(gEditor.getText().indexOf("ermahgerd"), 253, + "The correct source was loaded initially."); + is(gSources.selectedValue, gSources.values[0], + "The correct source is selected."); + + ok(gSources.selectedItem, + "There should be a selected source in the sources pane."); + ok(!gSources._selectedBreakpoint, + "There should be no selected breakpoint in the sources pane."); + is(gSources._conditionalPopupVisible, false, + "The breakpoint conditional expression popup should not be shown."); + + is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 0, + "There should be no visible stackframes."); + is(gDebugger.document.querySelectorAll(".dbg-breakpoint").length, 6, + "There should be thirteen visible breakpoints."); + }); + + gDebugger.gThreadClient.resume(); + + return finished; + } + + function testBreakpoint(line, highlightBreakpoint) { + // Highlight the breakpoint only if required. + if (highlightBreakpoint) { + let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line)); + gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: line }); + return finished; + } + + let selectedActor = gSources.selectedValue; + let selectedBreakpoint = gSources._selectedBreakpoint; + let selectedBreakpointItem = gSources._getBreakpoint(selectedBreakpoint); + let source = queries.getSource(getState(), selectedActor); + + ok(selectedActor, + "There should be a selected item in the sources pane."); + ok(selectedBreakpoint, + "There should be a selected breakpoint."); + ok(selectedBreakpointItem, + "There should be a selected breakpoint item in the sources pane."); + + is(selectedBreakpoint.location.actor, source.actor, + "The breakpoint on line " + line + " wasn't added on the correct source."); + is(selectedBreakpoint.location.line, line, + "The breakpoint on line " + line + " wasn't found."); + is(!!selectedBreakpoint.location.disabled, false, + "The breakpoint on line " + line + " should be enabled."); + is(gSources._conditionalPopupVisible, false, + "The breakpoint conditional expression popup should not have been shown."); + + isnot(selectedBreakpoint.condition, undefined, + "The breakpoint on line " + line + " should have a conditional expression."); + + ok(isCaretPos(gPanel, line), + "The editor caret position is not properly set."); + } + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretAndScopes(gPanel, 17); + callInTab(gTab, "ermahgerd"); + yield onCaretUpdated; + + yield actions.addBreakpoint( + { actor: gSources.selectedValue, line: 18 }, " 1a" + ); + yield actions.addBreakpoint( + { actor: gSources.selectedValue, line: 19 }, "new Error()" + ); + yield actions.addBreakpoint( + { actor: gSources.selectedValue, line: 20 }, "true" + ); + yield actions.addBreakpoint( + { actor: gSources.selectedValue, line: 21 }, "false" + ); + yield actions.addBreakpoint( + { actor: gSources.selectedValue, line: 22 }, "0" + ); + yield actions.addBreakpoint( + { actor: gSources.selectedValue, line: 23 }, "randomVar" + ); + + yield resumeAndTestBreakpoint(18); + yield resumeAndTestBreakpoint(19); + yield resumeAndTestBreakpoint(20); + yield resumeAndTestBreakpoint(23); + yield resumeAndTestNoBreakpoint(); + + // Reset traits back to default value + client.mainRoot.traits.conditionalBreakpoints = true; + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_console-eval.js b/devtools/client/debugger/test/mochitest/browser_dbg_console-eval.js new file mode 100644 index 000000000..37e0be1b1 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_console-eval.js @@ -0,0 +1,41 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Breaking in the middle of a script evaluated by the console should + * work + */ + +function test() { + Task.spawn(function* () { + let TAB_URL = EXAMPLE_URL + "doc_empty-tab-01.html"; + let [,, panel] = yield initDebugger(TAB_URL, { source: null }); + let dbgWin = panel.panelWin; + let sources = dbgWin.DebuggerView.Sources; + let frames = dbgWin.DebuggerView.StackFrames; + let editor = dbgWin.DebuggerView.editor; + let toolbox = gDevTools.getToolbox(panel.target); + + let paused = promise.all([ + waitForEditorEvents(panel, "cursorActivity"), + waitForDebuggerEvents(panel, dbgWin.EVENTS.SOURCE_SHOWN) + ]); + + toolbox.once("webconsole-ready", () => { + ok(toolbox.splitConsole, "Split console is shown."); + let jsterm = toolbox.getPanel("webconsole").hud.jsterm; + jsterm.execute("debugger"); + }); + EventUtils.synthesizeKey("VK_ESCAPE", {}, dbgWin); + + yield paused; + is(sources.selectedItem.attachment.label, "SCRIPT0", + "Anonymous source is selected in sources"); + ok(editor.getText() === "debugger", "Editor has correct text"); + + yield toolbox.closeSplitConsole(); + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_console-named-eval.js b/devtools/client/debugger/test/mochitest/browser_dbg_console-named-eval.js new file mode 100644 index 000000000..a6c3c96bb --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_console-named-eval.js @@ -0,0 +1,42 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Breaking in the middle of a named eval script created by the + * console should work + */ + +function test() { + Task.spawn(runTests); +} + +function* runTests() { + let TAB_URL = EXAMPLE_URL + "doc_empty-tab-01.html"; + let [,, panel] = yield initDebugger(TAB_URL, { source: null }); + let dbgWin = panel.panelWin; + let sources = dbgWin.DebuggerView.Sources; + let frames = dbgWin.DebuggerView.StackFrames; + let editor = dbgWin.DebuggerView.editor; + let toolbox = gDevTools.getToolbox(panel.target); + + let paused = waitForSourceAndCaretAndScopes(panel, "foo.js", 1); + + toolbox.once("webconsole-ready", () => { + ok(toolbox.splitConsole, "Split console is shown."); + let jsterm = toolbox.getPanel("webconsole").hud.jsterm; + jsterm.execute("eval('debugger; //# sourceURL=foo.js')"); + }); + EventUtils.synthesizeKey("VK_ESCAPE", {}, dbgWin); + + yield paused; + is(sources.selectedItem.attachment.label, "foo.js", + "New source is selected in sources"); + is(sources.selectedItem.attachment.group, "http://example.com", + "New source is in the right group"); + ok(editor.getText() === "debugger; //# sourceURL=foo.js", "Editor has correct text"); + + yield toolbox.closeSplitConsole(); + yield resumeDebuggerThenCloseAndFinish(panel); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-01.js new file mode 100644 index 000000000..9db259b98 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-01.js @@ -0,0 +1,106 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests the public evaluation API from the debugger controller. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + Task.spawn(function* () { + const options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + const [tab,, panel] = yield initDebugger(TAB_URL, options); + const win = panel.panelWin; + const frames = win.DebuggerController.StackFrames; + const framesView = win.DebuggerView.StackFrames; + const sourcesView = win.DebuggerView.Sources; + const editorView = win.DebuggerView.editor; + const events = win.EVENTS; + const queries = win.require("./content/queries"); + const constants = win.require("./content/constants"); + const actions = bindActionCreators(panel); + const getState = win.DebuggerController.getState; + + function checkView(frameDepth, selectedSource, caretLine, editorText) { + is(win.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(framesView.itemCount, 2, + "Should have four frames."); + is(framesView.selectedDepth, frameDepth, + "The correct frame is selected in the widget."); + is(sourcesView.selectedIndex, selectedSource, + "The correct source is selected in the widget."); + ok(isCaretPos(panel, caretLine), + "Editor caret location is correct."); + is(editorView.getText().search(editorText[0]), editorText[1], + "The correct source is not displayed."); + } + + // Cache the sources text to avoid having to wait for their + // retrieval. + const sources = queries.getSources(getState()); + yield promise.all(Object.keys(sources).map(k => { + return actions.loadSourceText(sources[k]); + })); + + is(Object.keys(getState().sources.sourcesText).length, 2, + "There should be two cached sources in the cache."); + + // Eval while not paused. + try { + yield frames.evaluate("foo"); + } catch (error) { + is(error.message, "No stack frame available.", + "Evaluating shouldn't work while the debuggee isn't paused."); + } + + callInTab(tab, "firstCall"); + yield waitForSourceAndCaretAndScopes(panel, "-02.js", 6); + checkView(0, 1, 6, [/secondCall/, 118]); + + // Eval in the topmost frame, while paused. + let updatedView = waitForDebuggerEvents(panel, events.FETCHED_SCOPES); + let result = yield frames.evaluate("foo"); + ok(!result.throw, "The evaluation hasn't thrown."); + is(result.return.type, "object", "The evaluation return type is correct."); + is(result.return.class, "Function", "The evaluation return class is correct."); + + yield updatedView; + checkView(0, 1, 6, [/secondCall/, 118]); + ok(true, "Evaluating in the topmost frame works properly."); + + // Eval in a different frame, while paused. + updatedView = waitForDebuggerEvents(panel, events.FETCHED_SCOPES); + try { + yield frames.evaluate("foo", { depth: 1 }); // oldest frame + } catch (result) { + is(result.return.type, "object", "The evaluation thrown type is correct."); + is(result.return.class, "Error", "The evaluation thrown class is correct."); + ok(!result.return, "The evaluation hasn't returned."); + } + + yield updatedView; + checkView(0, 1, 6, [/secondCall/, 118]); + ok(true, "Evaluating in a custom frame works properly."); + + // Eval in a non-existent frame, while paused. + waitForDebuggerEvents(panel, events.FETCHED_SCOPES).then(() => { + ok(false, "Shouldn't have updated the view when trying to evaluate " + + "an expression in a non-existent stack frame."); + }); + try { + yield frames.evaluate("foo", { depth: 4 }); // non-existent frame + } catch (error) { + is(error.message, "No stack frame available.", + "Evaluating shouldn't work if the specified frame doesn't exist."); + } + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-02.js new file mode 100644 index 000000000..ff4092e1d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_controller-evaluate-02.js @@ -0,0 +1,78 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests the public evaluation API from the debugger controller. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + Task.spawn(function* () { + const options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + const [tab,, panel] = yield initDebugger(TAB_URL, options); + const win = panel.panelWin; + const frames = win.DebuggerController.StackFrames; + const framesView = win.DebuggerView.StackFrames; + const sourcesView = win.DebuggerView.Sources; + const editorView = win.DebuggerView.editor; + const events = win.EVENTS; + const queries = win.require("./content/queries"); + const constants = win.require("./content/constants"); + const actions = bindActionCreators(panel); + const getState = win.DebuggerController.getState; + + function checkView(selectedFrame, selectedSource, caretLine, editorText) { + is(win.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(framesView.itemCount, 2, + "Should have four frames."); + is(framesView.selectedDepth, selectedFrame, + "The correct frame is selected in the widget."); + is(sourcesView.selectedIndex, selectedSource, + "The correct source is selected in the widget."); + ok(isCaretPos(panel, caretLine), + "Editor caret location is correct."); + is(editorView.getText().search(editorText[0]), editorText[1], + "The correct source is not displayed."); + } + + // Cache the sources text to avoid having to wait for their + // retrieval. + const sources = queries.getSources(getState()); + yield promise.all(Object.keys(sources).map(k => { + return actions.loadSourceText(sources[k]); + })); + + // Allow this generator function to yield first. + callInTab(tab, "firstCall"); + yield waitForSourceAndCaretAndScopes(panel, "-02.js", 6); + checkView(0, 1, 6, [/secondCall/, 118]); + + // Change the selected frame and eval inside it. + let updatedFrame = waitForDebuggerEvents(panel, events.FETCHED_SCOPES); + framesView.selectedDepth = 1; // oldest frame + yield updatedFrame; + checkView(1, 0, 5, [/firstCall/, 118]); + + let updatedView = waitForDebuggerEvents(panel, events.FETCHED_SCOPES); + try { + yield frames.evaluate("foo"); + } catch (result) { + is(result.return.type, "object", "The evaluation thrown type is correct."); + is(result.return.class, "Error", "The evaluation thrown class is correct."); + ok(!result.return, "The evaluation hasn't returned."); + } + + yield updatedView; + checkView(1, 0, 5, [/firstCall/, 118]); + ok(true, "Evaluating while in a user-selected frame works properly."); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_debugger-statement.js b/devtools/client/debugger/test/mochitest/browser_dbg_debugger-statement.js new file mode 100644 index 000000000..7378123f8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_debugger-statement.js @@ -0,0 +1,87 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests the behavior of the debugger statement. + */ + +const TAB_URL = EXAMPLE_URL + "doc_inline-debugger-statement.html"; + +var gClient; +var gTab; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + addTab(TAB_URL) + .then((aTab) => { + gTab = aTab; + return attachTabActorForUrl(gClient, TAB_URL); + }) + .then(testEarlyDebuggerStatement) + .then(testDebuggerStatement) + .then(() => gClient.close()) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testEarlyDebuggerStatement([aGrip, aResponse]) { + let deferred = promise.defer(); + + let onPaused = function (aEvent, aPacket) { + ok(false, "Pause shouldn't be called before we've attached!"); + deferred.reject(); + }; + + gClient.addListener("paused", onPaused); + + // This should continue without nesting an event loop and calling + // the onPaused hook, because we haven't attached yet. + callInTab(gTab, "runDebuggerStatement"); + + gClient.removeListener("paused", onPaused); + + // Now attach and resume... + gClient.request({ to: aResponse.threadActor, type: "attach" }, () => { + gClient.request({ to: aResponse.threadActor, type: "resume" }, () => { + ok(true, "Pause wasn't called before we've attached."); + deferred.resolve([aGrip, aResponse]); + }); + }); + + return deferred.promise; +} + +function testDebuggerStatement([aGrip, aResponse]) { + let deferred = promise.defer(); + + gClient.addListener("paused", (aEvent, aPacket) => { + gClient.request({ to: aResponse.threadActor, type: "resume" }, () => { + ok(true, "The pause handler was triggered on a debugger statement."); + deferred.resolve(); + }); + }); + + // Reach around the debugging protocol and execute the debugger statement. + callInTab(gTab, "runDebuggerStatement"); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_editor-contextmenu.js b/devtools/client/debugger/test/mochitest/browser_dbg_editor-contextmenu.js new file mode 100644 index 000000000..b42e3e123 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_editor-contextmenu.js @@ -0,0 +1,68 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 731394: Test the debugger source editor default context menu. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + let gTab, gPanel, gDebugger; + let gEditor, gSources, gContextMenu; + + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gContextMenu = gDebugger.document.getElementById("sourceEditorContextMenu"); + + waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest).then(null, info); + callInTab(gTab, "firstCall"); + }); + + function performTest() { + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(gSources.itemCount, 2, + "Found the expected number of sources."); + is(gEditor.getText().indexOf("debugger"), 166, + "The correct source was loaded initially."); + is(gSources.selectedValue, gSources.values[1], + "The correct source is selected."); + + is(gEditor.getText().indexOf("\u263a"), 162, + "Unicode characters are converted correctly."); + + ok(gContextMenu, + "The source editor's context menupopup is available."); + ok(gEditor.getOption("readOnly"), + "The source editor is read only."); + + gEditor.focus(); + gEditor.setSelection({ line: 1, ch: 0 }, { line: 1, ch: 10 }); + + once(gContextMenu, "popupshown").then(testContextMenu).then(null, info); + gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false); + } + + function testContextMenu() { + let document = gDebugger.document; + + ok(document.getElementById("editMenuCommands"), + "#editMenuCommands found."); + ok(!document.getElementById("editMenuKeys"), + "#editMenuKeys not found."); + + gContextMenu.hidePopup(); + resumeDebuggerThenCloseAndFinish(gPanel); + } +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_editor-mode.js b/devtools/client/debugger/test/mochitest/browser_dbg_editor-mode.js new file mode 100644 index 000000000..5982d9afd --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_editor-mode.js @@ -0,0 +1,97 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that updating the editor mode sets the right highlighting engine, + * and source URIs with extra query parameters also get the right engine. + */ + +const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js?a=b", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + + waitForSourceAndCaretAndScopes(gPanel, "code_test-editor-mode", 1) + .then(testInitialSource) + .then(testSwitch1) + .then(testSwitch2) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + callInTab(gTab, "firstCall"); + }); +} + +function testInitialSource() { + is(gSources.itemCount, 3, + "Found the expected number of sources."); + + is(gEditor.getMode().name, "text", + "Found the expected editor mode."); + is(gEditor.getText().search(/firstCall/), -1, + "The first source is not displayed."); + is(gEditor.getText().search(/debugger/), 135, + "The second source is displayed."); + is(gEditor.getText().search(/banana/), -1, + "The third source is not displayed."); + + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN); + gSources.selectedItem = e => e.attachment.label == "code_script-switching-01.js"; + return finished; +} + +function testSwitch1() { + is(gSources.itemCount, 3, + "Found the expected number of sources."); + + is(gEditor.getMode().name, "javascript", + "Found the expected editor mode."); + is(gEditor.getText().search(/firstCall/), 118, + "The first source is displayed."); + is(gEditor.getText().search(/debugger/), -1, + "The second source is not displayed."); + is(gEditor.getText().search(/banana/), -1, + "The third source is not displayed."); + + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN); + gSources.selectedItem = e => e.attachment.label == "doc_editor-mode.html"; + return finished; +} + +function testSwitch2() { + is(gSources.itemCount, 3, + "Found the expected number of sources."); + + is(gEditor.getMode().name, "htmlmixed", + "Found the expected editor mode."); + is(gEditor.getText().search(/firstCall/), -1, + "The first source is not displayed."); + is(gEditor.getText().search(/debugger/), -1, + "The second source is not displayed."); + is(gEditor.getText().search(/banana/), 443, + "The third source is displayed."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-01.js new file mode 100644 index 000000000..fa24e4507 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-01.js @@ -0,0 +1,147 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the eventListeners request works. + */ + +const TAB_URL = EXAMPLE_URL + "doc_event-listeners-01.html"; + +var gClient; +var gTab; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + addTab(TAB_URL) + .then((aTab) => { + gTab = aTab; + return attachThreadActorForUrl(gClient, TAB_URL); + }) + .then(pauseDebuggee) + .then(testEventListeners) + .then(() => gClient.close()) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function pauseDebuggee(aThreadClient) { + let deferred = promise.defer(); + + gClient.addOneTimeListener("paused", (aEvent, aPacket) => { + is(aPacket.type, "paused", + "We should now be paused."); + is(aPacket.why.type, "debuggerStatement", + "The debugger statement was hit."); + + deferred.resolve(aThreadClient); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + + return deferred.promise; +} + +function testEventListeners(aThreadClient) { + let deferred = promise.defer(); + + aThreadClient.eventListeners(aPacket => { + if (aPacket.error) { + let msg = "Error getting event listeners: " + aPacket.message; + ok(false, msg); + deferred.reject(msg); + return; + } + + is(aPacket.listeners.length, 3, + "Found all event listeners."); + + promise.all(aPacket.listeners.map(listener => { + const lDeferred = promise.defer(); + aThreadClient.pauseGrip(listener.function).getDefinitionSite(aResponse => { + if (aResponse.error) { + const msg = "Error getting function definition site: " + aResponse.message; + ok(false, msg); + lDeferred.reject(msg); + return; + } + listener.function.url = aResponse.source.url; + lDeferred.resolve(listener); + }); + return lDeferred.promise; + })).then(listeners => { + let types = []; + + for (let l of listeners) { + info("Listener for the " + l.type + " event."); + let node = l.node; + ok(node, "There is a node property."); + ok(node.object, "There is a node object property."); + ok(node.selector == "window" || + content.document.querySelectorAll(node.selector).length == 1, + "The node property is a unique CSS selector."); + + let func = l.function; + ok(func, "There is a function property."); + is(func.type, "object", "The function form is of type 'object'."); + is(func.class, "Function", "The function form is of class 'Function'."); + + // The onchange handler is an inline string that doesn't have + // a URL because it's basically eval'ed + if (l.type !== "change") { + is(func.url, TAB_URL, "The function url is correct."); + } + + is(l.allowsUntrusted, true, + "'allowsUntrusted' property has the right value."); + is(l.inSystemEventGroup, false, + "'inSystemEventGroup' property has the right value."); + + types.push(l.type); + + if (l.type == "keyup") { + is(l.capturing, true, + "Capturing property has the right value."); + is(l.isEventHandler, false, + "'isEventHandler' property has the right value."); + } else if (l.type == "load") { + is(l.capturing, false, + "Capturing property has the right value."); + is(l.isEventHandler, false, + "'isEventHandler' property has the right value."); + } else { + is(l.capturing, false, + "Capturing property has the right value."); + is(l.isEventHandler, true, + "'isEventHandler' property has the right value."); + } + } + + ok(types.indexOf("click") != -1, "Found the click handler."); + ok(types.indexOf("change") != -1, "Found the change handler."); + ok(types.indexOf("keyup") != -1, "Found the keyup handler."); + + aThreadClient.resume(deferred.resolve); + }); + }); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-02.js new file mode 100644 index 000000000..d7b13e4c5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-02.js @@ -0,0 +1,123 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the eventListeners request works when bound functions are used as + * event listeners. + */ + +const TAB_URL = EXAMPLE_URL + "doc_event-listeners-03.html"; + +var gClient; +var gTab; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + addTab(TAB_URL) + .then((aTab) => { + gTab = aTab; + return attachThreadActorForUrl(gClient, TAB_URL); + }) + .then(pauseDebuggee) + .then(testEventListeners) + .then(() => gClient.close()) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function pauseDebuggee(aThreadClient) { + let deferred = promise.defer(); + + gClient.addOneTimeListener("paused", (aEvent, aPacket) => { + is(aPacket.type, "paused", + "We should now be paused."); + is(aPacket.why.type, "debuggerStatement", + "The debugger statement was hit."); + + deferred.resolve(aThreadClient); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + + return deferred.promise; +} + +function testEventListeners(aThreadClient) { + let deferred = promise.defer(); + + aThreadClient.eventListeners(aPacket => { + if (aPacket.error) { + let msg = "Error getting event listeners: " + aPacket.message; + ok(false, msg); + deferred.reject(msg); + return; + } + + is(aPacket.listeners.length, 3, + "Found all event listeners."); + + promise.all(aPacket.listeners.map(listener => { + const lDeferred = promise.defer(); + aThreadClient.pauseGrip(listener.function).getDefinitionSite(aResponse => { + if (aResponse.error) { + const msg = "Error getting function definition site: " + aResponse.message; + ok(false, msg); + lDeferred.reject(msg); + return; + } + listener.function.url = aResponse.source.url; + lDeferred.resolve(listener); + }); + return lDeferred.promise; + })).then(listeners => { + is(listeners.length, 3, "Found three event listeners."); + for (let l of listeners) { + let node = l.node; + ok(node, "There is a node property."); + ok(node.object, "There is a node object property."); + ok(node.selector == "window" || + content.document.querySelectorAll(node.selector).length == 1, + "The node property is a unique CSS selector."); + + let func = l.function; + ok(func, "There is a function property."); + is(func.type, "object", "The function form is of type 'object'."); + is(func.class, "Function", "The function form is of class 'Function'."); + is(func.url, TAB_URL, "The function url is correct."); + + is(l.type, "click", "This is a click event listener."); + is(l.allowsUntrusted, true, + "'allowsUntrusted' property has the right value."); + is(l.inSystemEventGroup, false, + "'inSystemEventGroup' property has the right value."); + is(l.isEventHandler, false, + "'isEventHandler' property has the right value."); + is(l.capturing, false, + "Capturing property has the right value."); + } + + aThreadClient.resume(deferred.resolve); + }); + }); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-03.js new file mode 100644 index 000000000..8e193d8a6 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-03.js @@ -0,0 +1,82 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the eventListeners request works when there are event handlers + * that the debugger cannot unwrap. + */ + +const TAB_URL = EXAMPLE_URL + "doc_native-event-handler.html"; + +var gClient; +var gTab; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + addTab(TAB_URL) + .then((aTab) => { + gTab = aTab; + return attachThreadActorForUrl(gClient, TAB_URL); + }) + .then(pauseDebuggee) + .then(testEventListeners) + .then(() => gClient.close()) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function pauseDebuggee(aThreadClient) { + let deferred = promise.defer(); + + gClient.addOneTimeListener("paused", (aEvent, aPacket) => { + is(aPacket.type, "paused", + "We should now be paused."); + is(aPacket.why.type, "debuggerStatement", + "The debugger statement was hit."); + + deferred.resolve(aThreadClient); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + + return deferred.promise; +} + +function testEventListeners(aThreadClient) { + let deferred = promise.defer(); + + aThreadClient.eventListeners(aPacket => { + if (aPacket.error) { + let msg = "Error getting event listeners: " + aPacket.message; + ok(false, msg); + deferred.reject(msg); + return; + } + + // There are 3 event listeners in the page: button.onclick, window.onload + // and one more from the video element controls. + is(aPacket.listeners.length, 3, "Found all event listeners."); + aThreadClient.resume(deferred.resolve); + }); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-04.js new file mode 100644 index 000000000..f1b0036b3 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-04.js @@ -0,0 +1,55 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that event listeners are properly fetched even if one of the listeners + * don't have a Debugger.Source object (bug 942899). + * + * This test is skipped on debug and e10s builds for following reasons: + * - debug: requiring sdk/tabs causes memory leaks when new windows are opened + * in tests executed after this one. Bug 1142597. + * - e10s: tab.attach is not e10s safe and only works when add-on compatibility + * shims are in place. Bug 1146603. + */ + +const TAB_URL = EXAMPLE_URL + "doc_event-listeners-01.html"; + +function test() { + Task.spawn(function* () { + let tab = yield addTab(TAB_URL); + + // Create a sandboxed content script the Add-on SDK way. Inspired by bug + // 1145996. + let tabs = require("sdk/tabs"); + let sdkTab = [...tabs].find(tab => tab.url === TAB_URL); + ok(sdkTab, "Add-on SDK found the loaded tab."); + + info("Attaching an event handler via add-on sdk content scripts."); + let worker = sdkTab.attach({ + contentScript: "document.body.addEventListener('click', e => alert(e))", + onError: ok.bind(this, false) + }); + + let options = { + source: TAB_URL, + line: 1 + }; + let [,, panel, win] = yield initDebugger(tab, options); + let dbg = panel.panelWin; + let controller = dbg.DebuggerController; + let constants = dbg.require("./content/constants"); + let actions = dbg.require("./content/actions/event-listeners"); + let fetched = waitForDispatch(panel, constants.FETCH_EVENT_LISTENERS); + + info("Scheduling event listener fetch."); + controller.dispatch(actions.fetchEventListeners()); + + info("Waiting for updated event listeners to arrive."); + yield fetched; + + ok(true, "The listener update did not hang."); + closeDebuggerAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_file-reload.js b/devtools/client/debugger/test/mochitest/browser_dbg_file-reload.js new file mode 100644 index 000000000..cb7ceef8f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_file-reload.js @@ -0,0 +1,72 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that source contents are invalidated when the target navigates. + */ + +const TAB_URL = EXAMPLE_URL + "doc_random-javascript.html"; +const JS_URL = EXAMPLE_URL + "sjs_random-javascript.sjs"; + +function test() { + let options = { + source: JS_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gPanel = aPanel; + const gDebugger = aPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + Task.spawn(function* () { + let source = queries.getSelectedSource(getState()); + + is(queries.getSourceCount(getState()), 1, + "There should be one source displayed in the view."); + is(source.url, JS_URL, + "The correct source is currently selected in the view."); + ok(gEditor.getText().includes("bacon"), + "The currently shown source contains bacon. Mmm, delicious!"); + + const { text: firstText } = yield queries.getSourceText(getState(), source.actor); + const firstNumber = parseFloat(firstText.match(/\d\.\d+/)[0]); + + is(firstText, gEditor.getText(), + "gControllerSources.getText() returned the expected contents."); + ok(firstNumber <= 1 && firstNumber >= 0, + "The generated number seems to be created correctly."); + + yield reloadActiveTab(aPanel, gDebugger.EVENTS.SOURCE_SHOWN); + + is(queries.getSourceCount(getState()), 1, + "There should be one source displayed in the view."); + is(source.url, JS_URL, + "The correct source is currently selected in the view."); + ok(gEditor.getText().includes("bacon"), + "The newly shown source contains bacon. Mmm, delicious!"); + + source = queries.getSelectedSource(getState()); + const { text: secondText } = yield queries.getSourceText(getState(), source.actor); + const secondNumber = parseFloat(secondText.match(/\d\.\d+/)[0]); + + is(secondText, gEditor.getText(), + "gControllerSources.getText() returned the expected contents."); + ok(secondNumber <= 1 && secondNumber >= 0, + "The generated number seems to be created correctly."); + + isnot(firstText, secondText, + "The displayed sources were different across reloads."); + isnot(firstNumber, secondNumber, + "The displayed sources differences were correct across reloads."); + + yield closeDebuggerAndFinish(aPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_function-display-name.js b/devtools/client/debugger/test/mochitest/browser_dbg_function-display-name.js new file mode 100644 index 000000000..fe9ff19eb --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_function-display-name.js @@ -0,0 +1,68 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that anonymous functions appear in the stack frame list with either + * their displayName property or a SpiderMonkey-inferred name. + */ + +const TAB_URL = EXAMPLE_URL + "doc_function-display-name.html"; + +var gTab, gPanel, gDebugger; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + + testAnonCall(); + }); +} + +function testAnonCall() { + let onCaretUpdated = waitForCaretUpdated(gPanel, 15); + let onScopes = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES); + callInTab(gTab, "evalCall"); + promise.all([onCaretUpdated, onScopes]).then(() => { + ok(isCaretPos(gPanel, 15), + "The source editor caret position was incorrect."); + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 3, + "Should have three frames."); + is(gDebugger.document.querySelector("#stackframe-0 .dbg-stackframe-title").getAttribute("value"), + "anonFunc", "Frame name should be 'anonFunc'."); + + testInferredName(); + }); +} + +function testInferredName() { + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => { + ok(isCaretPos(gPanel, 15), + "The source editor caret position was incorrect."); + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 3, + "Should have three frames."); + is(gDebugger.document.querySelector("#stackframe-0 .dbg-stackframe-title").getAttribute("value"), + "a/<", "Frame name should be 'a/<'."); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); + + gDebugger.gThreadClient.resume(); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_global-method-override.js b/devtools/client/debugger/test/mochitest/browser_dbg_global-method-override.js new file mode 100644 index 000000000..ef2018b64 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_global-method-override.js @@ -0,0 +1,26 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that scripts that override properties of the global object, like + * toString don't break the debugger. The test page used to cause the debugger + * to throw when trying to attach to the thread actor. + */ + +const TAB_URL = EXAMPLE_URL + "doc_global-method-override.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + let gDebugger = aPanel.panelWin; + ok(gDebugger, "Should have a debugger available."); + is(gDebugger.gThreadClient.state, "attached", "Debugger should be attached."); + + closeDebuggerAndFinish(aPanel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js b/devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js new file mode 100644 index 000000000..3f1533a1f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_globalactor.js @@ -0,0 +1,61 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check extension-added global actor API. + */ + +const ACTORS_URL = CHROME_URL + "testactors.js"; + +function test() { + let gClient; + + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + DebuggerServer.addActors(ACTORS_URL); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + gClient.listTabs(aResponse => { + let globalActor = aResponse.testGlobalActor1; + ok(globalActor, "Found the test tab actor."); + ok(globalActor.includes("test_one"), + "testGlobalActor1's actorPrefix should be used."); + + gClient.request({ to: globalActor, type: "ping" }, aResponse => { + is(aResponse.pong, "pong", "Actor should respond to requests."); + + // Send another ping to see if the same actor is used. + gClient.request({ to: globalActor, type: "ping" }, aResponse => { + is(aResponse.pong, "pong", "Actor should respond to requests."); + + // Make sure that lazily-created actors are created only once. + let count = 0; + for (let connID of Object.getOwnPropertyNames(DebuggerServer._connections)) { + let conn = DebuggerServer._connections[connID]; + let actorPrefix = conn._prefix + "test_one"; + for (let pool of conn._extraPools) { + count += Object.keys(pool._actors).filter(e => { + return e.startsWith(actorPrefix); + }).length; + } + } + + is(count, 2, + "Only two actor exists in all pools. One tab actor and one global."); + + gClient.close().then(finish); + }); + }); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_hide-toolbar-buttons.js b/devtools/client/debugger/test/mochitest/browser_dbg_hide-toolbar-buttons.js new file mode 100644 index 000000000..2fb9f9ddb --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_hide-toolbar-buttons.js @@ -0,0 +1,34 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 1093349: Test that the pretty-printing and blackboxing buttons + * are hidden if the server doesn't support them + */ + +const TAB_URL = EXAMPLE_URL + "doc_auto-pretty-print-01.html"; + +var { RootActor } = require("devtools/server/actors/root"); + +function test() { + RootActor.prototype.traits.noBlackBoxing = true; + RootActor.prototype.traits.noPrettyPrinting = true; + + let options = { + source: EXAMPLE_URL + "code_ugly-5.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => { + let document = aPanel.panelWin.document; + let ppButton = document.querySelector("#pretty-print"); + let bbButton = document.querySelector("#black-box"); + let sep = document.querySelector("#sources-toolbar .devtools-separator"); + + is(ppButton.style.display, "none", "The pretty-print button is hidden"); + is(bbButton.style.display, "none", "The blackboxing button is hidden"); + is(sep.style.display, "none", "The separator is hidden"); + closeDebuggerAndFinish(aPanel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js b/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js new file mode 100644 index 000000000..82a23cada --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_host-layout.js @@ -0,0 +1,166 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This if the debugger's layout is correctly modified when the toolbox's + * host changes. + */ + +"use strict"; + +var gDefaultHostType = Services.prefs.getCharPref("devtools.toolbox.host"); + +function test() { + // test is too slow on some platforms due to the number of test cases + requestLongerTimeout(3); + + Task.spawn(function*() { + yield testHosts(["bottom", "side", "window:big"], ["horizontal", "vertical", "horizontal"]); + yield testHosts(["side", "bottom", "side"], ["vertical", "horizontal", "vertical"]); + yield testHosts(["bottom", "side", "bottom"], ["horizontal", "vertical", "horizontal"]); + yield testHosts(["side", "window:big", "side"], ["vertical", "horizontal", "vertical"]); + yield testHosts(["window:big", "side", "window:big"], ["horizontal", "vertical", "horizontal"]); + yield testHosts(["window:small", "bottom", "side"], ["vertical", "horizontal", "vertical"]); + yield testHosts(["window:small", "window:big", "window:small"], ["vertical", "horizontal", "vertical"]); + finish(); + }); +} + +function testHosts(aHostTypes, aLayoutTypes) { + let [firstHost, secondHost, thirdHost] = aHostTypes; + let [firstLayout, secondLayout, thirdLayout] = aLayoutTypes; + + Services.prefs.setCharPref("devtools.toolbox.host", getHost(firstHost)); + + return Task.spawn(function*() { + let [tab, debuggee, panel] = yield initDebugger(); + if (getHost(firstHost) === "window") { + yield resizeToolboxWindow(panel, firstHost); + } + + yield testHost(panel, getHost(firstHost), firstLayout); + yield switchAndTestHost(tab, panel, secondHost, secondLayout); + yield switchAndTestHost(tab, panel, thirdHost, thirdLayout); + yield teardown(panel); + }); +} + +function switchAndTestHost(aTab, aPanel, aHostType, aLayoutType) { + let gToolbox = aPanel._toolbox; + let gDebugger = aPanel.panelWin; + + return Task.spawn(function*() { + let layoutChanged = waitEventOnce(gDebugger, gDebugger.EVENTS.LAYOUT_CHANGED); + let hostChanged = gToolbox.switchHost(getHost(aHostType)); + + yield hostChanged; + info("The toolbox's host has changed."); + + if (getHost(aHostType) === "window") { + yield resizeToolboxWindow(aPanel, aHostType); + } + + yield layoutChanged; + info("The debugger's layout has changed."); + + yield testHost(aPanel, getHost(aHostType), aLayoutType); + }); +} + +function waitEventOnce(aTarget, aEvent) { + let deferred = promise.defer(); + aTarget.once(aEvent, deferred.resolve); + return deferred.promise; +} + +function getHost(host) { + if (host.indexOf("window") == 0) { + return "window"; + } + return host; +} + +function resizeToolboxWindow(panel, host) { + let sizeOption = host.split(":")[1]; + let win = panel._toolbox.win.parent; + + // should be the same value as BREAKPOINT_SMALL_WINDOW_WIDTH in debugger-view.js + let breakpoint = 850; + + if (sizeOption == "big" && win.outerWidth <= breakpoint) { + yield resizeAndWaitForLayoutChange(panel, breakpoint + 300); + } else if (sizeOption == "small" && win.outerWidth >= breakpoint) { + yield resizeAndWaitForLayoutChange(panel, breakpoint - 300); + } else { + info("Window resize unnecessary for host " + host); + } +} + +function resizeAndWaitForLayoutChange(panel, width) { + info("Updating toolbox window width to " + width); + + let win = panel._toolbox.win.parent; + let gDebugger = panel.panelWin; + + win.resizeTo(width, window.screen.availHeight); + yield waitEventOnce(gDebugger, gDebugger.EVENTS.LAYOUT_CHANGED); +} + +function testHost(aPanel, aHostType, aLayoutType) { + let gDebugger = aPanel.panelWin; + let gView = gDebugger.DebuggerView; + + is(gView._hostType, aHostType, + "The default host type should've been set on the panel window (1)."); + is(gDebugger.gHostType, aHostType, + "The default host type should've been set on the panel window (2)."); + + is(gView._body.getAttribute("layout"), aLayoutType, + "The default host type is present as an attribute on the panel's body."); + + if (aLayoutType == "horizontal") { + is(gView._workersAndSourcesPane.parentNode.id, "debugger-widgets", + "The workers and sources pane's parent is correct for the horizontal layout."); + is(gView._instrumentsPane.parentNode.id, "editor-and-instruments-pane", + "The instruments pane's parent is correct for the horizontal layout."); + } else { + is(gView._workersAndSourcesPane.parentNode.id, "vertical-layout-panes-container", + "The workers and sources pane's parent is correct for the vertical layout."); + is(gView._instrumentsPane.parentNode.id, "vertical-layout-panes-container", + "The instruments pane's parent is correct for the vertical layout."); + } + + let widgets = gDebugger.document.getElementById("debugger-widgets").childNodes; + let content = gDebugger.document.getElementById("debugger-content").childNodes; + let editorPane = + gDebugger.document.getElementById("editor-and-instruments-pane").childNodes; + let verticalPane = + gDebugger.document.getElementById("vertical-layout-panes-container").childNodes; + + if (aLayoutType == "horizontal") { + is(widgets.length, 5, // 1 pane, 1 content box, 2 splitters and a phantom box. + "Found the correct number of debugger widgets."); + is(content.length, 1, // 1 pane + "Found the correct number of debugger content."); + is(editorPane.length, 3, // 2 panes, 1 splitter + "Found the correct number of debugger panes."); + is(verticalPane.length, 1, // 1 lonely splitter in the phantom box. + "Found the correct number of debugger panes."); + } else { + is(widgets.length, 4, // 1 content box, 2 splitters and a phantom box. + "Found the correct number of debugger widgets."); + is(content.length, 1, // 1 pane + "Found the correct number of debugger content."); + is(editorPane.length, 2, // 1 pane, 1 splitter + "Found the correct number of debugger panes."); + is(verticalPane.length, 3, // 2 panes and 1 splitter in the phantom box. + "Found the correct number of debugger panes."); + } +} + +registerCleanupFunction(function() { + Services.prefs.setCharPref("devtools.toolbox.host", gDefaultHostType); + gDefaultHostType = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_iframes.js b/devtools/client/debugger/test/mochitest/browser_dbg_iframes.js new file mode 100644 index 000000000..afc3f9682 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_iframes.js @@ -0,0 +1,72 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that iframes can be added as debuggees. + */ + +const TAB_URL = EXAMPLE_URL + "doc_iframes.html"; + +function test() { + let gTab, gDebuggee, gPanel, gDebugger; + let gIframe, gEditor, gSources, gFrames; + + let options = { + source: EXAMPLE_URL + "doc_inline-debugger-statement.html", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => { + gTab = aTab; + gDebuggee = aDebuggee; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gIframe = gDebuggee.frames[0]; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gFrames = gDebugger.DebuggerView.StackFrames; + + checkIframeSource(); + checkIframePause() + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); + + function checkIframeSource() { + is(gDebugger.gThreadClient.paused, false, + "Should be running after starting the test."); + + ok(isCaretPos(gPanel, 1), + "The source editor caret position was incorrect."); + is(gFrames.itemCount, 0, + "Should have only no frames."); + + is(gSources.itemCount, 1, + "Found the expected number of entries in the sources widget."); + is(gEditor.getText().indexOf("debugger"), 348, + "The correct source was loaded initially."); + is(getSelectedSourceURL(gSources), EXAMPLE_URL + "doc_inline-debugger-statement.html", + "The currently selected source value is incorrect (0)."); + is(gSources.selectedValue, gSources.values[0], + "The currently selected source value is incorrect (1)."); + } + + function checkIframePause() { + // Spin the event loop before causing the debuggee to pause, to allow + // this function to return first. + executeSoon(() => gIframe.runDebuggerStatement()); + + return waitForCaretAndScopes(gPanel, 16).then(() => { + is(gDebugger.gThreadClient.paused, true, + "Should be paused after an interrupt request."); + + ok(isCaretPos(gPanel, 16), + "The source editor caret position was incorrect."); + is(gFrames.itemCount, 1, + "Should have only one frame."); + }); + } +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse.js b/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse.js new file mode 100644 index 000000000..31b3318cd --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse.js @@ -0,0 +1,167 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the debugger panes collapse properly. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +var gTab, gPanel, gDebugger; +var gPrefs, gOptions; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + + let [aTab,, aPanel] = yield initDebugger(TAB_URL, options); + + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gPrefs = gDebugger.Prefs; + gOptions = gDebugger.DebuggerView.Options; + + testPanesState(); + + gDebugger.DebuggerView.toggleInstrumentsPane({ visible: true, animated: false }); + + yield testInstrumentsPaneCollapse(); + testPanesStartupPref(); + + closeDebuggerAndFinish(gPanel); + }); +} + +function testPanesState() { + let instrumentsPane = + gDebugger.document.getElementById("instruments-pane"); + let instrumentsPaneToggleButton = + gDebugger.document.getElementById("instruments-pane-toggle"); + + ok(instrumentsPane.classList.contains("pane-collapsed") && + instrumentsPaneToggleButton.classList.contains("pane-collapsed"), + "The debugger view instruments pane should initially be hidden."); + is(gPrefs.panesVisibleOnStartup, false, + "The debugger view instruments pane should initially be preffed as hidden."); + isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true", + "The options menu item should not be checked."); +} + +function* testInstrumentsPaneCollapse () { + let instrumentsPane = + gDebugger.document.getElementById("instruments-pane"); + let instrumentsPaneToggleButton = + gDebugger.document.getElementById("instruments-pane-toggle"); + + let width = parseInt(instrumentsPane.getAttribute("width")); + is(width, gPrefs.instrumentsWidth, + "The instruments pane has an incorrect width."); + is(instrumentsPane.style.marginLeft, "0px", + "The instruments pane has an incorrect left margin."); + is(instrumentsPane.style.marginRight, "0px", + "The instruments pane has an incorrect right margin."); + ok(!instrumentsPane.hasAttribute("animated"), + "The instruments pane has an incorrect animated attribute."); + ok(!instrumentsPane.classList.contains("pane-collapsed") && + !instrumentsPaneToggleButton.classList.contains("pane-collapsed"), + "The instruments pane should at this point be visible."); + + // Trigger reflow to make sure the UI is in required state. + gDebugger.document.documentElement.getBoundingClientRect(); + + gDebugger.DebuggerView.toggleInstrumentsPane({ visible: false, animated: true }); + + yield once(instrumentsPane, "transitionend"); + + is(gPrefs.panesVisibleOnStartup, false, + "The debugger view panes should still initially be preffed as hidden."); + isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true", + "The options menu item should still not be checked."); + + let margin = -(width + 1) + "px"; + is(width, gPrefs.instrumentsWidth, + "The instruments pane has an incorrect width after collapsing."); + is(instrumentsPane.style.marginLeft, margin, + "The instruments pane has an incorrect left margin after collapsing."); + is(instrumentsPane.style.marginRight, margin, + "The instruments pane has an incorrect right margin after collapsing."); + + ok(!instrumentsPane.hasAttribute("animated"), + "The instruments pane has an incorrect attribute after an animated collapsing."); + ok(instrumentsPane.classList.contains("pane-collapsed") && + instrumentsPaneToggleButton.classList.contains("pane-collapsed"), + "The instruments pane should not be visible after collapsing."); + + gDebugger.DebuggerView.toggleInstrumentsPane({ visible: true, animated: false }); + + is(gPrefs.panesVisibleOnStartup, false, + "The debugger view panes should still initially be preffed as hidden."); + isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true", + "The options menu item should still not be checked."); + + is(width, gPrefs.instrumentsWidth, + "The instruments pane has an incorrect width after uncollapsing."); + is(instrumentsPane.style.marginLeft, "0px", + "The instruments pane has an incorrect left margin after uncollapsing."); + is(instrumentsPane.style.marginRight, "0px", + "The instruments pane has an incorrect right margin after uncollapsing."); + ok(!instrumentsPane.hasAttribute("animated"), + "The instruments pane has an incorrect attribute after an unanimated uncollapsing."); + ok(!instrumentsPane.classList.contains("pane-collapsed") && + !instrumentsPaneToggleButton.classList.contains("pane-collapsed"), + "The instruments pane should be visible again after uncollapsing."); +} + +function testPanesStartupPref() { + let instrumentsPane = + gDebugger.document.getElementById("instruments-pane"); + let instrumentsPaneToggleButton = + gDebugger.document.getElementById("instruments-pane-toggle"); + + is(gPrefs.panesVisibleOnStartup, false, + "The debugger view panes should still initially be preffed as hidden."); + + ok(!instrumentsPane.classList.contains("pane-collapsed") && + !instrumentsPaneToggleButton.classList.contains("pane-collapsed"), + "The debugger instruments pane should at this point be visible."); + is(gPrefs.panesVisibleOnStartup, false, + "The debugger view panes should initially be preffed as hidden."); + isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true", + "The options menu item should still not be checked."); + + gOptions._showPanesOnStartupItem.setAttribute("checked", "true"); + gOptions._toggleShowPanesOnStartup(); + + ok(!instrumentsPane.classList.contains("pane-collapsed") && + !instrumentsPaneToggleButton.classList.contains("pane-collapsed"), + "The debugger instruments pane should at this point be visible."); + is(gPrefs.panesVisibleOnStartup, true, + "The debugger view panes should now be preffed as visible."); + is(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true", + "The options menu item should now be checked."); + + gOptions._showPanesOnStartupItem.setAttribute("checked", "false"); + gOptions._toggleShowPanesOnStartup(); + + ok(!instrumentsPane.classList.contains("pane-collapsed") && + !instrumentsPaneToggleButton.classList.contains("pane-collapsed"), + "The debugger instruments pane should at this point be visible."); + is(gPrefs.panesVisibleOnStartup, false, + "The debugger view panes should now be preffed as hidden."); + isnot(gOptions._showPanesOnStartupItem.getAttribute("checked"), "true", + "The options menu item should now be unchecked."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gPrefs = null; + gOptions = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse_keyboard.js b/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse_keyboard.js new file mode 100644 index 000000000..c26c476cc --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_instruments-pane-collapse_keyboard.js @@ -0,0 +1,40 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the debugger panes collapse properly. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +function test() { + initDebugger(TAB_URL).then(([aTab,, aPanel]) => { + Task.spawn(function* () { + let doc = aPanel.panelWin.document; + let panel = doc.getElementById("instruments-pane"); + let button = doc.getElementById("instruments-pane-toggle"); + ok(panel.classList.contains("pane-collapsed"), + "The instruments panel is initially in collapsed state"); + + yield togglePane(button, "Press on the toggle button to expand", panel, "VK_RETURN"); + ok(!panel.classList.contains("pane-collapsed"), + "The instruments panel is in the expanded state"); + + yield togglePane(button, "Press on the toggle button to collapse", panel, "VK_SPACE"); + ok(panel.classList.contains("pane-collapsed"), + "The instruments panel is in the collapsed state"); + + closeDebuggerAndFinish(aPanel); + }); + }); +} + +function* togglePane(button, message, pane, keycode) { + let onTransitionEnd = once(pane, "transitionend"); + info(message); + button.focus(); + EventUtils.synthesizeKey(keycode, {}); + yield onTransitionEnd; +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_interrupts.js b/devtools/client/debugger/test/mochitest/browser_dbg_interrupts.js new file mode 100644 index 000000000..d6d61a76b --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_interrupts.js @@ -0,0 +1,123 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test resuming from button and keyboard shortcuts. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + let gTab, gPanel, gDebugger; + let gSources, gBreakpoints, gTarget, gResumeButton, gResumeKey, gThreadClient; + + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + gBreakpoints = gDebugger.DebuggerController.Breakpoints; + gTarget = gDebugger.gTarget; + gThreadClient = gDebugger.gThreadClient; + gResumeButton = gDebugger.document.getElementById("resume"); + gResumeKey = gDebugger.document.getElementById("resumeKey"); + + gTarget.on("thread-paused", failOnPause); + addBreakpoints() + .then(() => { gTarget.off("thread-paused", failOnPause); }) + .then(testResumeButton) + .then(testResumeKeyboard) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); + + function failOnPause() { + ok(false, "A pause was sent, but it shouldn't have been"); + } + + function addBreakpoints() { + return promise.resolve(null) + .then(() => gPanel.addBreakpoint({ actor: gSources.values[0], line: 5 })) + .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 6 })) + .then(() => gPanel.addBreakpoint({ actor: gSources.values[1], line: 7 })) + .then(() => ensureThreadClientState(gPanel, "resumed")); + } + + function resume() { + let onceResumed = gTarget.once("thread-resumed"); + gThreadClient.resume(); + return onceResumed; + } + + function testResumeButton() { + info("Pressing the resume button, expecting a thread-paused"); + + ok(!gResumeButton.hasAttribute("disabled"), "Resume button is not disabled 1"); + ok(!gResumeButton.hasAttribute("break-on-next"), "Resume button isn't waiting for next execution"); + ok(!gResumeButton.hasAttribute("checked"), "Resume button is not checked"); + let oncePaused = gTarget.once("thread-paused"); + + // Click the pause button to break on next execution + EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger); + ok(gResumeButton.hasAttribute("disabled"), "Resume button is disabled"); + ok(gResumeButton.hasAttribute("break-on-next"), "Resume button is waiting for next execution"); + ok(!gResumeButton.hasAttribute("checked"), "Resume button is not checked"); + + // Evaluate a script to fully pause the debugger + once(gDebugger.gClient, "willInterrupt").then(() => { + evalInTab(gTab, "1+1;"); + }); + + return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN) + .then(() => { + ok(!gResumeButton.hasAttribute("break-on-next"), "Resume button isn't waiting for next execution"); + is(gResumeButton.getAttribute("checked"), "true", "Resume button is checked"); + ok(!gResumeButton.hasAttribute("disabled"), "Resume button is not disabled 2"); + }) + .then(() => { + let p = ensureThreadClientState(gPanel, "resumed"); + gThreadClient.resume(); + return p; + }); + } + + function testResumeKeyboard() { + let key = gResumeKey.getAttribute("keycode"); + info("Triggering a pause with keyboard (" + key + "), expecting a thread-paused"); + + ok(!gResumeButton.hasAttribute("disabled"), "Resume button is not disabled 3"); + ok(!gResumeButton.hasAttribute("break-on-next"), "Resume button isn't waiting for next execution"); + ok(!gResumeButton.hasAttribute("checked"), "Resume button is not checked"); + + // Press the key to break on next execution + EventUtils.synthesizeKey(key, { }, gDebugger); + ok(gResumeButton.hasAttribute("disabled"), "Resume button is disabled"); + ok(gResumeButton.hasAttribute("break-on-next"), "Resume button is waiting for next execution"); + ok(!gResumeButton.hasAttribute("checked"), "Resume button is not checked"); + + // Evaluate a script to fully pause the debugger + once(gDebugger.gClient, "willInterrupt").then(() => { + evalInTab(gTab, "1+1;"); + }); + + return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN) + .then(() => { + ok(!gResumeButton.hasAttribute("break-on-next"), "Resume button isn't waiting for next execution"); + is(gResumeButton.getAttribute("checked"), "true", "Resume button is checked"); + ok(!gResumeButton.hasAttribute("disabled"), "Resume button is not disabled 4"); + }) + .then(() => { + let p = ensureThreadClientState(gPanel, "resumed"); + gThreadClient.resume(); + return p; + }); + } +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_jump-to-function-definition.js b/devtools/client/debugger/test/mochitest/browser_dbg_jump-to-function-definition.js new file mode 100644 index 000000000..71d9c340c --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_jump-to-function-definition.js @@ -0,0 +1,50 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the jump to function definition works properly. + */ + +const TAB_URL = EXAMPLE_URL + "doc_function-jump.html"; +const SCRIPT_URI = EXAMPLE_URL + "code_function-jump-01.js"; + + +function test() { + let gTab, gPanel, gDebugger, gSources; + + let options = { + source: EXAMPLE_URL + "code_function-jump-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + + jumpToFunctionDefinition() + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); + + function jumpToFunctionDefinition() { + let callLocation = {line: 5, ch: 0}; + let editor = gDebugger.DebuggerView.editor; + let coords = editor.getCoordsFromPosition(callLocation); + + gDebugger.DebuggerView.Sources._onMouseDown({ clientX: coords.left, + clientY: coords.top, + metaKey: true }); + + let deferred = promise.defer(); + executeSoon(() => { + is(editor.getDebugLocation(), 1, "foo definition should be highlighted"); + deferred.resolve(); + }); + return deferred.promise; + } +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_listaddons.js b/devtools/client/debugger/test/mochitest/browser_dbg_listaddons.js new file mode 100644 index 000000000..1490c9670 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_listaddons.js @@ -0,0 +1,112 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure the listAddons request works as specified. + */ +const ADDON1_ID = "jid1-oBAwBoE5rSecNg@jetpack"; +const ADDON1_PATH = "addon1.xpi"; +const ADDON2_ID = "jid1-qjtzNGV8xw5h2A@jetpack"; +const ADDON2_PATH = "addon2.xpi"; + +var gAddon1, gAddon1Actor, gAddon2, gAddon2Actor, gClient; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + promise.resolve(null) + .then(testFirstAddon) + .then(testSecondAddon) + .then(testRemoveFirstAddon) + .then(testRemoveSecondAddon) + .then(() => gClient.close()) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testFirstAddon() { + let addonListChanged = false; + gClient.addOneTimeListener("addonListChanged", () => { + addonListChanged = true; + }); + + return addTemporaryAddon(ADDON1_PATH).then(aAddon => { + gAddon1 = aAddon; + + return getAddonActorForId(gClient, ADDON1_ID).then(aGrip => { + ok(!addonListChanged, "Should not yet be notified that list of addons changed."); + ok(aGrip, "Should find an addon actor for addon1."); + gAddon1Actor = aGrip.actor; + }); + }); +} + +function testSecondAddon() { + let addonListChanged = false; + gClient.addOneTimeListener("addonListChanged", function () { + addonListChanged = true; + }); + + return addTemporaryAddon(ADDON2_PATH).then(aAddon => { + gAddon2 = aAddon; + + return getAddonActorForId(gClient, ADDON1_ID).then(aFirstGrip => { + return getAddonActorForId(gClient, ADDON2_ID).then(aSecondGrip => { + ok(addonListChanged, "Should be notified that list of addons changed."); + is(aFirstGrip.actor, gAddon1Actor, "First addon's actor shouldn't have changed."); + ok(aSecondGrip, "Should find a addon actor for the second addon."); + gAddon2Actor = aSecondGrip.actor; + }); + }); + }); +} + +function testRemoveFirstAddon() { + let addonListChanged = false; + gClient.addOneTimeListener("addonListChanged", function () { + addonListChanged = true; + }); + + return removeAddon(gAddon1).then(() => { + return getAddonActorForId(gClient, ADDON1_ID).then(aGrip => { + ok(addonListChanged, "Should be notified that list of addons changed."); + ok(!aGrip, "Shouldn't find a addon actor for the first addon anymore."); + }); + }); +} + +function testRemoveSecondAddon() { + let addonListChanged = false; + gClient.addOneTimeListener("addonListChanged", function () { + addonListChanged = true; + }); + + return removeAddon(gAddon2).then(() => { + return getAddonActorForId(gClient, ADDON2_ID).then(aGrip => { + ok(addonListChanged, "Should be notified that list of addons changed."); + ok(!aGrip, "Shouldn't find a addon actor for the second addon anymore."); + }); + }); +} + +registerCleanupFunction(function () { + gAddon1 = null; + gAddon1Actor = null; + gAddon2 = null; + gAddon2Actor = null; + gClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js new file mode 100644 index 000000000..dc804713b --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js @@ -0,0 +1,98 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure the listTabs request works as specified. + */ + +const TAB1_URL = EXAMPLE_URL + "doc_empty-tab-01.html"; +const TAB2_URL = EXAMPLE_URL + "doc_empty-tab-02.html"; + +var gTab1, gTab1Actor, gTab2, gTab2Actor, gClient; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + promise.resolve(null) + .then(testFirstTab) + .then(testSecondTab) + .then(testRemoveTab) + .then(testAttachRemovedTab) + .then(() => gClient.close()) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testFirstTab() { + return addTab(TAB1_URL).then(aTab => { + gTab1 = aTab; + + return getTabActorForUrl(gClient, TAB1_URL).then(aGrip => { + ok(aGrip, "Should find a tab actor for the first tab."); + gTab1Actor = aGrip.actor; + }); + }); +} + +function testSecondTab() { + return addTab(TAB2_URL).then(aTab => { + gTab2 = aTab; + + return getTabActorForUrl(gClient, TAB1_URL).then(aFirstGrip => { + return getTabActorForUrl(gClient, TAB2_URL).then(aSecondGrip => { + is(aFirstGrip.actor, gTab1Actor, "First tab's actor shouldn't have changed."); + ok(aSecondGrip, "Should find a tab actor for the second tab."); + gTab2Actor = aSecondGrip.actor; + }); + }); + }); +} + +function testRemoveTab() { + return removeTab(gTab1).then(() => { + return getTabActorForUrl(gClient, TAB1_URL).then(aGrip => { + ok(!aGrip, "Shouldn't find a tab actor for the first tab anymore."); + }); + }); +} + +function testAttachRemovedTab() { + return removeTab(gTab2).then(() => { + let deferred = promise.defer(); + + gClient.addListener("paused", (aEvent, aPacket) => { + ok(false, "Attaching to an exited tab actor shouldn't generate a pause."); + deferred.reject(); + }); + + gClient.request({ to: gTab2Actor, type: "attach" }, aResponse => { + is(aResponse.error, "connectionClosed", + "Connection is gone since the tab was removed."); + deferred.resolve(); + }); + + return deferred.promise; + }); +} + +registerCleanupFunction(function () { + gTab1 = null; + gTab1Actor = null; + gTab2 = null; + gTab2Actor = null; + gClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-02.js new file mode 100644 index 000000000..f696b6cb0 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-02.js @@ -0,0 +1,219 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure the root actor's live tab list implementation works as specified. + */ + +var { BrowserTabList } = require("devtools/server/actors/webbrowser"); + +var gTestPage = "data:text/html;charset=utf-8," + encodeURIComponent( + "<title>JS Debugger BrowserTabList test page</title><body>Yo.</body>"); + +// The tablist object whose behavior we observe. +var gTabList; +var gFirstActor, gActorA; +var gTabA, gTabB, gTabC; +var gNewWindow; + +// Stock onListChanged handler. +var onListChangedCount = 0; +function onListChangedHandler() { + onListChangedCount++; +} + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + gTabList = new BrowserTabList("fake DebuggerServerConnection"); + gTabList._testing = true; + gTabList.onListChanged = onListChangedHandler; + + checkSingleTab() + .then(addTabA) + .then(testTabA) + .then(addTabB) + .then(testTabB) + .then(removeTabA) + .then(testTabClosed) + .then(addTabC) + .then(testTabC) + .then(removeTabC) + .then(testNewWindow) + .then(removeNewWindow) + .then(testWindowClosed) + .then(removeTabB) + .then(checkSingleTab) + .then(finishUp); +} + +function checkSingleTab() { + return gTabList.getList().then(aTabActors => { + is(aTabActors.length, 1, "initial tab list: contains initial tab"); + gFirstActor = aTabActors[0]; + is(gFirstActor.url, "about:blank", "initial tab list: initial tab URL is 'about:blank'"); + is(gFirstActor.title, "New Tab", "initial tab list: initial tab title is 'New Tab'"); + }); +} + +function addTabA() { + return addTab(gTestPage).then(aTab => { + gTabA = aTab; + }); +} + +function testTabA() { + is(onListChangedCount, 1, "onListChanged handler call count"); + + return gTabList.getList().then(aTabActors => { + let tabActors = new Set(aTabActors); + is(tabActors.size, 2, "gTabA opened: two tabs in list"); + ok(tabActors.has(gFirstActor), "gTabA opened: initial tab present"); + + info("actors: " + [...tabActors].map(a => a.url)); + gActorA = [...tabActors].filter(a => a !== gFirstActor)[0]; + ok(gActorA.url.match(/^data:text\/html;/), "gTabA opened: new tab URL"); + is(gActorA.title, "JS Debugger BrowserTabList test page", "gTabA opened: new tab title"); + }); +} + +function addTabB() { + return addTab(gTestPage).then(aTab => { + gTabB = aTab; + }); +} + +function testTabB() { + is(onListChangedCount, 2, "onListChanged handler call count"); + + return gTabList.getList().then(aTabActors => { + let tabActors = new Set(aTabActors); + is(tabActors.size, 3, "gTabB opened: three tabs in list"); + }); +} + +function removeTabA() { + let deferred = promise.defer(); + + once(gBrowser.tabContainer, "TabClose").then(aEvent => { + ok(!aEvent.detail.adoptedBy, "This was a normal tab close"); + + // Let the actor's TabClose handler finish first. + executeSoon(deferred.resolve); + }, false); + + removeTab(gTabA); + return deferred.promise; +} + +function testTabClosed() { + is(onListChangedCount, 3, "onListChanged handler call count"); + + gTabList.getList().then(aTabActors => { + let tabActors = new Set(aTabActors); + is(tabActors.size, 2, "gTabA closed: two tabs in list"); + ok(tabActors.has(gFirstActor), "gTabA closed: initial tab present"); + + info("actors: " + [...tabActors].map(a => a.url)); + gActorA = [...tabActors].filter(a => a !== gFirstActor)[0]; + ok(gActorA.url.match(/^data:text\/html;/), "gTabA closed: new tab URL"); + is(gActorA.title, "JS Debugger BrowserTabList test page", "gTabA closed: new tab title"); + }); +} + +function addTabC() { + return addTab(gTestPage).then(aTab => { + gTabC = aTab; + }); +} + +function testTabC() { + is(onListChangedCount, 4, "onListChanged handler call count"); + + gTabList.getList().then(aTabActors => { + let tabActors = new Set(aTabActors); + is(tabActors.size, 3, "gTabC opened: three tabs in list"); + }); +} + +function removeTabC() { + let deferred = promise.defer(); + + once(gBrowser.tabContainer, "TabClose").then(aEvent => { + ok(aEvent.detail.adoptedBy, "This was a tab closed by moving"); + + // Let the actor's TabClose handler finish first. + executeSoon(deferred.resolve); + }, false); + + gNewWindow = gBrowser.replaceTabWithWindow(gTabC); + return deferred.promise; +} + +function testNewWindow() { + is(onListChangedCount, 5, "onListChanged handler call count"); + + return gTabList.getList().then(aTabActors => { + let tabActors = new Set(aTabActors); + is(tabActors.size, 3, "gTabC closed: three tabs in list"); + ok(tabActors.has(gFirstActor), "gTabC closed: initial tab present"); + + info("actors: " + [...tabActors].map(a => a.url)); + gActorA = [...tabActors].filter(a => a !== gFirstActor)[0]; + ok(gActorA.url.match(/^data:text\/html;/), "gTabC closed: new tab URL"); + is(gActorA.title, "JS Debugger BrowserTabList test page", "gTabC closed: new tab title"); + }); +} + +function removeNewWindow() { + let deferred = promise.defer(); + + once(gNewWindow, "unload").then(aEvent => { + ok(!aEvent.detail, "This was a normal window close"); + + // Let the actor's TabClose handler finish first. + executeSoon(deferred.resolve); + }, false); + + gNewWindow.close(); + return deferred.promise; +} + +function testWindowClosed() { + is(onListChangedCount, 6, "onListChanged handler call count"); + + return gTabList.getList().then(aTabActors => { + let tabActors = new Set(aTabActors); + is(tabActors.size, 2, "gNewWindow closed: two tabs in list"); + ok(tabActors.has(gFirstActor), "gNewWindow closed: initial tab present"); + + info("actors: " + [...tabActors].map(a => a.url)); + gActorA = [...tabActors].filter(a => a !== gFirstActor)[0]; + ok(gActorA.url.match(/^data:text\/html;/), "gNewWindow closed: new tab URL"); + is(gActorA.title, "JS Debugger BrowserTabList test page", "gNewWindow closed: new tab title"); + }); +} + +function removeTabB() { + let deferred = promise.defer(); + + once(gBrowser.tabContainer, "TabClose").then(aEvent => { + ok(!aEvent.detail.adoptedBy, "This was a normal tab close"); + + // Let the actor's TabClose handler finish first. + executeSoon(deferred.resolve); + }, false); + + removeTab(gTabB); + return deferred.promise; +} + +function finishUp() { + gTabList = gFirstActor = gActorA = gTabA = gTabB = gTabC = gNewWindow = null; + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-03.js new file mode 100644 index 000000000..d5584dcdb --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-03.js @@ -0,0 +1,61 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure the listTabs request works as specified. + */ + +const TAB1_URL = EXAMPLE_URL + "doc_empty-tab-01.html"; + +var gTab1, gTab1Actor, gTab2, gTab2Actor, gClient; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(Task.async(function* ([aType, aTraits]) { + is(aType, "browser", + "Root actor should identify itself as a browser."); + let tab = yield addTab(TAB1_URL); + + let { tabs } = yield gClient.listTabs(); + is(tabs.length, 2, "Should be two tabs"); + let tabGrip = tabs.filter(a => a.url == TAB1_URL).pop(); + ok(tabGrip, "Should have an actor for the tab"); + + let response = yield gClient.request({ to: tabGrip.actor, type: "attach" }); + is(response.type, "tabAttached", "Should have attached"); + + response = yield gClient.listTabs(); + tabs = response.tabs; + + response = yield gClient.request({ to: tabGrip.actor, type: "detach" }); + is(response.type, "detached", "Should have detached"); + + let newGrip = tabs.filter(a => a.url == TAB1_URL).pop(); + is(newGrip.actor, tabGrip.actor, "Should have the same actor for the same tab"); + + response = yield gClient.request({ to: tabGrip.actor, type: "attach" }); + is(response.type, "tabAttached", "Should have attached"); + response = yield gClient.request({ to: tabGrip.actor, type: "detach" }); + is(response.type, "detached", "Should have detached"); + + yield removeTab(tab); + yield gClient.close(); + finish(); + })); +} + +registerCleanupFunction(function () { + gTab1 = null; + gTab1Actor = null; + gTab2 = null; + gTab2Actor = null; + gClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_listworkers.js b/devtools/client/debugger/test/mochitest/browser_dbg_listworkers.js new file mode 100644 index 000000000..315acc231 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_listworkers.js @@ -0,0 +1,59 @@ +var TAB_URL = EXAMPLE_URL + "doc_listworkers-tab.html"; +var WORKER1_URL = "code_listworkers-worker1.js"; +var WORKER2_URL = "code_listworkers-worker2.js"; + +function test() { + Task.spawn(function* () { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let tab = yield addTab(TAB_URL); + let { tabs } = yield listTabs(client); + let [, tabClient] = yield attachTab(client, findTab(tabs, TAB_URL)); + + let { workers } = yield listWorkers(tabClient); + is(workers.length, 0); + + executeSoon(() => { + evalInTab(tab, "var worker1 = new Worker('" + WORKER1_URL + "');"); + }); + yield waitForWorkerListChanged(tabClient); + + ({ workers } = yield listWorkers(tabClient)); + is(workers.length, 1); + is(workers[0].url, WORKER1_URL); + + executeSoon(() => { + evalInTab(tab, "var worker2 = new Worker('" + WORKER2_URL + "');"); + }); + yield waitForWorkerListChanged(tabClient); + + ({ workers } = yield listWorkers(tabClient)); + is(workers.length, 2); + is(workers[0].url, WORKER1_URL); + is(workers[1].url, WORKER2_URL); + + executeSoon(() => { + evalInTab(tab, "worker1.terminate()"); + }); + yield waitForWorkerListChanged(tabClient); + + ({ workers } = yield listWorkers(tabClient)); + is(workers.length, 1); + is(workers[0].url, WORKER2_URL); + + executeSoon(() => { + evalInTab(tab, "worker2.terminate()"); + }); + yield waitForWorkerListChanged(tabClient); + + ({ workers } = yield listWorkers(tabClient)); + is(workers.length, 0); + + yield close(client); + finish(); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-01-simple.js b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-01-simple.js new file mode 100644 index 000000000..261881835 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-01-simple.js @@ -0,0 +1,60 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that changing the tab location URL works. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const gFrames = gDebugger.DebuggerView.StackFrames; + const constants = gDebugger.require("./content/constants"); + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretAndScopes(gPanel, 14); + callInTab(gTab, "simpleCall"); + yield onCaretUpdated; + + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + + is(gFrames.itemCount, 1, + "Should have only one frame."); + + is(gSources.itemCount, 1, + "Found the expected number of entries in the sources widget."); + + isnot(gSources.selectedValue, null, + "There should be a selected source value."); + isnot(gEditor.getText().length, 0, + "The source editor should have some text displayed."); + isnot(gEditor.getText(), gDebugger.L10N.getStr("loadingText"), + "The source editor text should not be 'Loading...'"); + + is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-notice-container").length, 0, + "The sources widget should not display any notice at this point (1)."); + is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-notice").length, 0, + "The sources widget should not display any notice at this point (2)."); + is(gDebugger.document.querySelector("#sources .side-menu-widget-empty-notice > label"), null, + "The sources widget should not display a notice at this point (3)."); + + yield doResume(gPanel); + navigateActiveTabTo(gPanel, "about:blank"); + yield waitForDispatch(gPanel, constants.UNLOAD); + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-02-blank.js b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-02-blank.js new file mode 100644 index 000000000..10c7d98ba --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-02-blank.js @@ -0,0 +1,57 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that changing the tab location URL to a page with no sources works. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const gFrames = gDebugger.DebuggerView.StackFrames; + const constants = gDebugger.require("./content/constants"); + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretUpdated(gPanel, 14); + callInTab(gTab, "simpleCall"); + yield onCaretUpdated; + + navigateActiveTabTo(gPanel, "about:blank"); + yield waitForNavigation(gPanel); + + isnot(gDebugger.gThreadClient.state, "paused", + "Should not be paused after a tab navigation."); + + is(gFrames.itemCount, 0, + "Should have no frames."); + + is(gSources.itemCount, 0, + "Found no entries in the sources widget."); + + is(gSources.selectedValue, "", + "There should be no selected source value."); + is(gEditor.getText().length, 0, + "The source editor should not have any text displayed."); + + is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-text").length, 1, + "The sources widget should now display a notice (1)."); + is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-text")[0].getAttribute("value"), + gDebugger.L10N.getStr("noSourcesText"), + "The sources widget should now display a notice (2)."); + + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-03-new.js b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-03-new.js new file mode 100644 index 000000000..878c7be81 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-03-new.js @@ -0,0 +1,59 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that changing the tab location URL to a page with other sources works. + */ + +const TAB_URL_1 = EXAMPLE_URL + "doc_recursion-stack.html"; +const TAB_URL_2 = EXAMPLE_URL + "doc_iframes.html"; + +function test() { + let options = { + source: TAB_URL_1, + line: 1 + }; + initDebugger(TAB_URL_1, options).then(([aTab, aDebuggee, aPanel]) => { + const gTab = aTab; + const gDebuggee = aDebuggee; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const gFrames = gDebugger.DebuggerView.StackFrames; + const constants = gDebugger.require("./content/constants"); + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretUpdated(gPanel, 14); + callInTab(gTab, "simpleCall"); + yield onCaretUpdated; + + const startedLoading = waitForNextDispatch(gDebugger.DebuggerController, + constants.LOAD_SOURCE_TEXT); + navigateActiveTabTo(gPanel, TAB_URL_2, gDebugger.EVENTS.SOURCE_SHOWN); + yield startedLoading; + + isnot(gDebugger.gThreadClient.state, "paused", + "Should not be paused after a tab navigation."); + is(gFrames.itemCount, 0, + "Should have no frames."); + is(gSources.itemCount, 1, + "Found the expected number of entries in the sources widget."); + + is(getSelectedSourceURL(gSources), EXAMPLE_URL + "doc_inline-debugger-statement.html", + "There should be a selected source value."); + isnot(gEditor.getText().length, 0, + "The source editor should have some text displayed."); + is(gEditor.getText(), gDebugger.L10N.getStr("loadingText"), + "The source editor text should be 'Loading...'"); + + is(gDebugger.document.querySelectorAll("#sources .side-menu-widget-empty-text").length, 0, + "The sources widget should not display any notice at this point."); + + yield waitForDispatch(gPanel, constants.LOAD_SOURCE_TEXT); + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-04-breakpoint.js b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-04-breakpoint.js new file mode 100644 index 000000000..493796720 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_location-changes-04-breakpoint.js @@ -0,0 +1,165 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that reloading a page with a breakpoint set does not cause it to + * fire more than once. + */ + +const TAB_URL = EXAMPLE_URL + "doc_included-script.html"; +const SOURCE_URL = EXAMPLE_URL + "code_location-changes.js"; + +function test() { + const options = { + source: SOURCE_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => { + const gTab = aTab; + const gDebuggee = aDebuggee; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + function clickButtonAndPause() { + const paused = waitForPause(gDebugger.gThreadClient); + BrowserTestUtils.synthesizeMouse("button", 2, 2, {}, gBrowser.selectedBrowser); + return paused; + } + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretUpdated(gPanel, 17); + callInTab(gTab, "runDebuggerStatement"); + yield onCaretUpdated; + + const location = { actor: getSourceActor(gSources, SOURCE_URL), line: 5 }; + yield actions.addBreakpoint(location); + + const caretUpdated = waitForSourceAndCaret(gPanel, ".js", 5); + gSources.highlightBreakpoint(location); + yield caretUpdated; + ok(true, "Switched to the desired function when adding a breakpoint"); + + is(gDebugger.gThreadClient.state, "paused", + "The breakpoint was hit (1)."); + is(getSelectedSourceURL(gSources), SOURCE_URL, + "The currently shown source is correct (1)."); + ok(isCaretPos(gPanel, 5), + "The source editor caret position is correct (1)."); + + yield doResume(gPanel); + + isnot(gDebugger.gThreadClient.state, "paused", + "The breakpoint was not hit yet (2)."); + is(getSelectedSourceURL(gSources), SOURCE_URL, + "The currently shown source is correct (2)."); + ok(isCaretPos(gPanel, 5), + "The source editor caret position is correct (2)."); + + let packet = yield clickButtonAndPause(); + is(packet.why.type, "breakpoint", + "Execution has advanced to the breakpoint."); + isnot(packet.why.type, "debuggerStatement", + "The breakpoint was hit before the debugger statement."); + yield ensureCaretAt(gPanel, 5, 1, true); + + is(gDebugger.gThreadClient.state, "paused", + "The breakpoint was hit (3)."); + is(getSelectedSourceURL(gSources), SOURCE_URL, + "The currently shown source is incorrect (3)."); + ok(isCaretPos(gPanel, 5), + "The source editor caret position is incorrect (3)."); + + let paused = waitForPause(gDebugger.gThreadClient); + gDebugger.gThreadClient.resume(); + packet = yield paused; + + is(packet.why.type, "debuggerStatement", + "Execution has advanced to the next line."); + isnot(packet.why.type, "breakpoint", + "No ghost breakpoint was hit."); + + yield ensureCaretAt(gPanel, 6, 1, true); + + is(gDebugger.gThreadClient.state, "paused", + "The debugger statement was hit (4)."); + is(getSelectedSourceURL(gSources), SOURCE_URL, + "The currently shown source is incorrect (4)."); + ok(isCaretPos(gPanel, 6), + "The source editor caret position is incorrect (4)."); + + yield promise.all([ + reload(gPanel), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN) + ]); + + isnot(gDebugger.gThreadClient.state, "paused", + "The breakpoint wasn't hit yet (5)."); + is(getSelectedSourceURL(gSources), SOURCE_URL, + "The currently shown source is incorrect (5)."); + ok(isCaretPos(gPanel, 1), + "The source editor caret position is incorrect (5)."); + + paused = waitForPause(gDebugger.gThreadClient); + clickButtonAndPause(); + packet = yield paused; + is(packet.why.type, "breakpoint", + "Execution has advanced to the breakpoint."); + isnot(packet.why.type, "debuggerStatement", + "The breakpoint was hit before the debugger statement."); + yield ensureCaretAt(gPanel, 5, 1, true); + + is(gDebugger.gThreadClient.state, "paused", + "The breakpoint was hit (6)."); + is(getSelectedSourceURL(gSources), SOURCE_URL, + "The currently shown source is incorrect (6)."); + ok(isCaretPos(gPanel, 5), + "The source editor caret position is incorrect (6)."); + + paused = waitForPause(gDebugger.gThreadClient); + gDebugger.gThreadClient.resume(); + packet = yield paused; + + is(packet.why.type, "debuggerStatement", + "Execution has advanced to the next line."); + isnot(packet.why.type, "breakpoint", + "No ghost breakpoint was hit."); + + yield ensureCaretAt(gPanel, 6, 1, true); + + is(gDebugger.gThreadClient.state, "paused", + "The debugger statement was hit (7)."); + is(getSelectedSourceURL(gSources), SOURCE_URL, + "The currently shown source is incorrect (7)."); + ok(isCaretPos(gPanel, 6), + "The source editor caret position is incorrect (7)."); + + let sourceShown = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN); + // Click the second source in the list. + yield actions.selectSource(getSourceForm(gSources, TAB_URL)); + yield sourceShown; + is(gEditor.getText().indexOf("debugger"), 447, + "The correct source is shown in the source editor."); + is(gEditor.getBreakpoints().length, 0, + "No breakpoints should be shown for the second source."); + yield ensureCaretAt(gPanel, 1, 1, true); + + sourceShown = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN); + yield actions.selectSource(getSourceForm(gSources, SOURCE_URL)); + yield sourceShown; + is(gEditor.getText().indexOf("debugger"), 148, + "The correct source is shown in the source editor."); + is(gEditor.getBreakpoints().length, 1, + "One breakpoint should be shown for the first source."); + + yield ensureCaretAt(gPanel, 6, 1, true); + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_multiple-windows.js b/devtools/client/debugger/test/mochitest/browser_dbg_multiple-windows.js new file mode 100644 index 000000000..b0bb1834c --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_multiple-windows.js @@ -0,0 +1,165 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the debugger attaches to the right tab when multiple windows + * are open. + */ + +const TAB1_URL = EXAMPLE_URL + "doc_script-switching-01.html"; +const TAB2_URL = EXAMPLE_URL + "doc_script-switching-02.html"; + +var gNewTab, gNewWindow; +var gClient; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + promise.resolve(null) + .then(() => addTab(TAB1_URL)) + .then(testFirstTab) + .then(() => addWindow(TAB2_URL)) + .then(testNewWindow) + .then(testFocusFirst) + .then(testRemoveTab) + .then(() => gClient.close()) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testFirstTab(aTab) { + let deferred = promise.defer(); + + gNewTab = aTab; + ok(!!gNewTab, "Second tab created."); + + gClient.listTabs(aResponse => { + let tabActor = aResponse.tabs.filter(aGrip => aGrip.url == TAB1_URL).pop(); + ok(tabActor, + "Should find a tab actor for the first tab."); + + is(aResponse.selected, 1, + "The first tab is selected."); + + deferred.resolve(); + }); + + return deferred.promise; +} + +function testNewWindow(aWindow) { + let deferred = promise.defer(); + + gNewWindow = aWindow; + ok(!!gNewWindow, "Second window created."); + + gNewWindow.focus(); + + let topWindow = Services.wm.getMostRecentWindow("navigator:browser"); + is(topWindow, gNewWindow, + "The second window is on top."); + + let isActive = promise.defer(); + let isLoaded = promise.defer(); + + promise.all([isActive.promise, isLoaded.promise]).then(() => { + gClient.listTabs(aResponse => { + is(aResponse.selected, 2, + "The second tab is selected."); + + deferred.resolve(); + }); + }); + + if (Services.focus.activeWindow != gNewWindow) { + gNewWindow.addEventListener("activate", function onActivate(aEvent) { + if (aEvent.target != gNewWindow) { + return; + } + gNewWindow.removeEventListener("activate", onActivate, true); + isActive.resolve(); + }, true); + } else { + isActive.resolve(); + } + + let contentLocation = gNewWindow.content.location.href; + if (contentLocation != TAB2_URL) { + gNewWindow.document.addEventListener("load", function onLoad(aEvent) { + if (aEvent.target.documentURI != TAB2_URL) { + return; + } + gNewWindow.document.removeEventListener("load", onLoad, true); + isLoaded.resolve(); + }, true); + } else { + isLoaded.resolve(); + } + + return deferred.promise; +} + +function testFocusFirst() { + let deferred = promise.defer(); + + once(window.content, "focus").then(() => { + gClient.listTabs(aResponse => { + is(aResponse.selected, 1, + "The first tab is selected after focusing on it."); + + deferred.resolve(); + }); + }); + + window.content.focus(); + + return deferred.promise; +} + +function testRemoveTab() { + let deferred = promise.defer(); + + gNewWindow.close(); + + // give it time to close + executeSoon(function () { continue_remove_tab(deferred); }); + return deferred.promise; +} + +function continue_remove_tab(deferred) +{ + removeTab(gNewTab); + + gClient.listTabs(aResponse => { + // Verify that tabs are no longer included in listTabs. + let foundTab1 = aResponse.tabs.some(aGrip => aGrip.url == TAB1_URL); + let foundTab2 = aResponse.tabs.some(aGrip => aGrip.url == TAB2_URL); + ok(!foundTab1, "Tab1 should be gone."); + ok(!foundTab2, "Tab2 should be gone."); + + is(aResponse.selected, 0, + "The original tab is selected."); + + deferred.resolve(); + }); +} + +registerCleanupFunction(function () { + gNewTab = null; + gNewWindow = null; + gClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_navigation.js b/devtools/client/debugger/test/mochitest/browser_dbg_navigation.js new file mode 100644 index 000000000..df48601e6 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_navigation.js @@ -0,0 +1,75 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check tab attach/navigation. + */ + +const TAB1_URL = EXAMPLE_URL + "doc_empty-tab-01.html"; +const TAB2_URL = EXAMPLE_URL + "doc_empty-tab-02.html"; + +var gClient; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + addTab(TAB1_URL) + .then(() => attachTabActorForUrl(gClient, TAB1_URL)) + .then(testNavigate) + .then(testDetach) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testNavigate([aGrip, aResponse]) { + let outstanding = [promise.defer(), promise.defer()]; + + gClient.addListener("tabNavigated", function onTabNavigated(aEvent, aPacket) { + is(aPacket.url, TAB2_URL, + "Got a tab navigation notification."); + + if (aPacket.state == "start") { + ok(true, "Tab started to navigate."); + outstanding[0].resolve(); + } else { + ok(true, "Tab finished navigating."); + gClient.removeListener("tabNavigated", onTabNavigated); + outstanding[1].resolve(); + } + }); + + gBrowser.selectedBrowser.loadURI(TAB2_URL); + return promise.all(outstanding.map(e => e.promise)) + .then(() => aGrip.actor); +} + +function testDetach(aActor) { + let deferred = promise.defer(); + + gClient.addOneTimeListener("tabDetached", (aType, aPacket) => { + ok(true, "Got a tab detach notification."); + is(aPacket.from, aActor, "tab detach message comes from the expected actor"); + deferred.resolve(gClient.close()); + }); + + removeTab(gBrowser.selectedTab); + return deferred.promise; +} + +registerCleanupFunction(function () { + gClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_no-dangling-breakpoints.js b/devtools/client/debugger/test/mochitest/browser_dbg_no-dangling-breakpoints.js new file mode 100644 index 000000000..b55e0132d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_no-dangling-breakpoints.js @@ -0,0 +1,25 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 1201008 - Make sure you can't set a breakpoint in a blank + * editor + */ + +function test() { + initDebugger('data:text/html,hi', { source: null }).then(([aTab,, aPanel]) => { + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + + Task.spawn(function* () { + const editor = gDebugger.DebuggerView.editor; + editor.emit("gutterClick", 0); + is(editor.getBreakpoints().length, 0, + "A breakpoint should not exist"); + + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_no-page-sources.js b/devtools/client/debugger/test/mochitest/browser_dbg_no-page-sources.js new file mode 100644 index 000000000..ff7e6f04a --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_no-page-sources.js @@ -0,0 +1,54 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure the right text shows when the page has no sources. + */ + +const TAB_URL = EXAMPLE_URL + "doc_no-page-sources.html"; + +var gTab, gDebuggee, gPanel, gDebugger; +var gEditor, gSources; + +function test() { + initDebugger(TAB_URL, { source: null }).then(([aTab, aDebuggee, aPanel]) => { + gTab = aTab; + gDebuggee = aDebuggee; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + const constants = gDebugger.require("./content/constants"); + + reloadActiveTab(gPanel); + waitForNavigation(gPanel) + .then(testSourcesEmptyText) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testSourcesEmptyText() { + is(gSources.itemCount, 0, + "Found no entries in the sources widget."); + + is(gEditor.getText().length, 0, + "The source editor should not have any text displayed."); + + is(gDebugger.document.querySelector("#sources .side-menu-widget-empty-text").getAttribute("value"), + gDebugger.L10N.getStr("noSourcesText"), + "The sources widget should now display 'This page has no sources'."); +} + +registerCleanupFunction(function () { + gTab = null; + gDebuggee = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_on-pause-highlight.js b/devtools/client/debugger/test/mochitest/browser_dbg_on-pause-highlight.js new file mode 100644 index 000000000..07e2360af --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_on-pause-highlight.js @@ -0,0 +1,86 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that debugger's tab is highlighted when it is paused and not the + * currently selected tool. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +var gTab, gPanel, gDebugger; +var gToolbox, gToolboxTab; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gToolbox = gPanel._toolbox; + gToolboxTab = gToolbox.doc.getElementById("toolbox-tab-jsdebugger"); + + testPause(); + }); +} + +function testPause() { + is(gDebugger.gThreadClient.paused, false, + "Should be running after starting test."); + + gDebugger.gThreadClient.addOneTimeListener("paused", () => { + gToolbox.selectTool("webconsole").then(() => { + ok(gToolboxTab.hasAttribute("highlighted") && + gToolboxTab.getAttribute("highlighted") == "true", + "The highlighted class is present"); + ok(!gToolboxTab.hasAttribute("selected") || + gToolboxTab.getAttribute("selected") != "true", + "The tab is not selected"); + }).then(() => gToolbox.selectTool("jsdebugger")).then(() => { + ok(gToolboxTab.hasAttribute("highlighted") && + gToolboxTab.getAttribute("highlighted") == "true", + "The highlighted class is present"); + ok(gToolboxTab.hasAttribute("selected") && + gToolboxTab.getAttribute("selected") == "true", + "...and the tab is selected, so the glow will not be present."); + }).then(testResume); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + + // Evaluate a script to fully pause the debugger + once(gDebugger.gClient, "willInterrupt").then(() => { + evalInTab(gTab, "1+1;"); + }); +} + +function testResume() { + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + gToolbox.selectTool("webconsole").then(() => { + ok(!gToolboxTab.classList.contains("highlighted"), + "The highlighted class is not present now after the resume"); + ok(!gToolboxTab.hasAttribute("selected") || + gToolboxTab.getAttribute("selected") != "true", + "The tab is not selected"); + }).then(() => closeDebuggerAndFinish(gPanel)); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gToolbox = null; + gToolboxTab = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_on-pause-raise.js b/devtools/client/debugger/test/mochitest/browser_dbg_on-pause-raise.js new file mode 100644 index 000000000..6f6f15247 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_on-pause-raise.js @@ -0,0 +1,120 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the toolbox is raised when the debugger gets paused. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +add_task(function *() { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let panelWin = panel.panelWin; + let toolbox = panel._toolbox; + let toolboxTab = toolbox.doc.getElementById("toolbox-tab-jsdebugger"); + + let newTab = yield addTab(TAB_URL); + isnot(newTab, tab, + "The newly added tab is different from the debugger's tab."); + is(gBrowser.selectedTab, newTab, + "Debugger's tab is not the selected tab."); + + info("Run tests against bottom host."); + yield testPause(); + yield testResume(); + + // testResume selected the console, select back the debugger. + yield toolbox.selectTool("jsdebugger"); + + info("Switching to a toolbox window host."); + yield toolbox.switchHost(Toolbox.HostType.WINDOW); + + info("Run tests against window host."); + yield testPause(); + yield testResume(); + + info("Cleanup after the test."); + yield toolbox.switchHost(Toolbox.HostType.BOTTOM); + yield closeDebuggerAndFinish(panel); + + function* testPause() { + is(panelWin.gThreadClient.paused, false, + "Should be running after starting the test."); + + let onFocus, onTabSelect; + if (toolbox.hostType == Toolbox.HostType.WINDOW) { + onFocus = new Promise(done => { + toolbox.win.parent.addEventListener("focus", function onFocus() { + toolbox.win.parent.removeEventListener("focus", onFocus, true); + done(); + }, true); + }); + } else { + onTabSelect = new Promise(done => { + tab.parentNode.addEventListener("TabSelect", function listener({type}) { + tab.parentNode.removeEventListener(type, listener); + done(); + }); + }); + } + + let onPaused = waitForPause(panelWin.gThreadClient); + + // Evaluate a script to fully pause the debugger + evalInTab(tab, "debugger;"); + + yield onPaused; + yield onFocus; + yield onTabSelect; + + if (toolbox.hostType != Toolbox.HostType.WINDOW) { + is(gBrowser.selectedTab, tab, + "Debugger's tab got selected."); + } + + yield toolbox.selectTool("webconsole"); + ok(toolboxTab.hasAttribute("highlighted") && + toolboxTab.getAttribute("highlighted") == "true", + "The highlighted class is present"); + ok(!toolboxTab.hasAttribute("selected") || + toolboxTab.getAttribute("selected") != "true", + "The tab is not selected"); + yield toolbox.selectTool("jsdebugger"); + ok(toolboxTab.hasAttribute("highlighted") && + toolboxTab.getAttribute("highlighted") == "true", + "The highlighted class is present"); + ok(toolboxTab.hasAttribute("selected") && + toolboxTab.getAttribute("selected") == "true", + "...and the tab is selected, so the glow will not be present."); + } + + function* testResume() { + let onPaused = waitForEvent(panelWin.gThreadClient, "resumed"); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + panelWin.document.getElementById("resume"), + panelWin); + + yield onPaused; + + yield toolbox.selectTool("webconsole"); + ok(!toolboxTab.hasAttribute("highlighted") || + toolboxTab.getAttribute("highlighted") != "true", + "The highlighted class is not present now after the resume"); + ok(!toolboxTab.hasAttribute("selected") || + toolboxTab.getAttribute("selected") != "true", + "The tab is not selected"); + } +}); + +registerCleanupFunction(function () { + // Revert to the default toolbox host, so that the following tests proceed + // normally and not inside a non-default host. + Services.prefs.setCharPref("devtools.toolbox.host", Toolbox.HostType.BOTTOM); +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_optimized-out-vars.js b/devtools/client/debugger/test/mochitest/browser_dbg_optimized-out-vars.js new file mode 100644 index 000000000..ba60a0068 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_optimized-out-vars.js @@ -0,0 +1,50 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that optimized out variables aren't present in the variables view. + +function test() { + Task.spawn(function* () { + const TAB_URL = EXAMPLE_URL + "doc_closure-optimized-out.html"; + let gDebugger, sources; + + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + gDebugger = panel.panelWin; + sources = gDebugger.DebuggerView.Sources; + + yield panel.addBreakpoint({ actor: sources.values[0], + line: 18 }); + yield ensureThreadClientState(panel, "resumed"); + + // Spin the event loop before causing the debuggee to pause, to allow + // this function to return first. + generateMouseClickInTab(tab, "content.document.querySelector('button')"); + + yield waitForDebuggerEvents(panel, gDebugger.EVENTS.FETCHED_SCOPES); + let gVars = gDebugger.DebuggerView.Variables; + let outerScope = gVars.getScopeAtIndex(1); + outerScope.expand(); + + let upvarVar = outerScope.get("upvar"); + ok(upvarVar, "The variable `upvar` is shown."); + is(upvarVar.target.querySelector(".value").getAttribute("value"), + gDebugger.L10N.getStr("variablesViewOptimizedOut"), + "Should show the optimized out message for upvar."); + + let argVar = outerScope.get("arg"); + is(argVar.target.querySelector(".name").getAttribute("value"), "arg", + "Should have the right property name for |arg|."); + is(argVar.target.querySelector(".value").getAttribute("value"), 42, + "Should have the right property value for |arg|."); + + yield resumeDebuggerThenCloseAndFinish(panel); + }).then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_panel-size.js b/devtools/client/debugger/test/mochitest/browser_dbg_panel-size.js new file mode 100644 index 000000000..32e2df0c7 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_panel-size.js @@ -0,0 +1,88 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the sources and instruments panels widths are properly + * remembered when the debugger closes. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +function test() { + let gTab, gPanel, gDebugger; + let gPrefs, gSources, gInstruments; + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gPrefs = gDebugger.Prefs; + gSources = gDebugger.document.getElementById("workers-and-sources-pane"); + gInstruments = gDebugger.document.getElementById("instruments-pane"); + + performTest(); + }); + + function performTest() { + let preferredWsw = Services.prefs.getIntPref("devtools.debugger.ui.panes-workers-and-sources-width"); + let preferredIw = Services.prefs.getIntPref("devtools.debugger.ui.panes-instruments-width"); + let someWidth1, someWidth2; + + do { + someWidth1 = parseInt(Math.random() * 200) + 100; + someWidth2 = parseInt(Math.random() * 300) + 100; + } while ((someWidth1 == preferredWsw) || (someWidth2 == preferredIw)); + + info("Preferred sources width: " + preferredWsw); + info("Preferred instruments width: " + preferredIw); + info("Generated sources width: " + someWidth1); + info("Generated instruments width: " + someWidth2); + + ok(gPrefs.workersAndSourcesWidth, + "The debugger preferences should have a saved workersAndSourcesWidth value."); + ok(gPrefs.instrumentsWidth, + "The debugger preferences should have a saved instrumentsWidth value."); + + is(gPrefs.workersAndSourcesWidth, preferredWsw, + "The debugger preferences should have a correct workersAndSourcesWidth value."); + is(gPrefs.instrumentsWidth, preferredIw, + "The debugger preferences should have a correct instrumentsWidth value."); + + is(gSources.getAttribute("width"), gPrefs.workersAndSourcesWidth, + "The workers and sources pane width should be the same as the preferred value."); + is(gInstruments.getAttribute("width"), gPrefs.instrumentsWidth, + "The instruments pane width should be the same as the preferred value."); + + gSources.setAttribute("width", someWidth1); + gInstruments.setAttribute("width", someWidth2); + + is(gPrefs.workersAndSourcesWidth, preferredWsw, + "The workers and sources pane width pref should still be the same as the preferred value."); + is(gPrefs.instrumentsWidth, preferredIw, + "The instruments pane width pref should still be the same as the preferred value."); + + isnot(gSources.getAttribute("width"), gPrefs.workersAndSourcesWidth, + "The workers and sources pane width should not be the preferred value anymore."); + isnot(gInstruments.getAttribute("width"), gPrefs.instrumentsWidth, + "The instruments pane width should not be the preferred value anymore."); + + teardown(gPanel).then(() => { + is(gPrefs.workersAndSourcesWidth, someWidth1, + "The workers and sources pane width should have been saved by now."); + is(gPrefs.instrumentsWidth, someWidth2, + "The instruments pane width should have been saved by now."); + + // Cleanup after ourselves! + Services.prefs.setIntPref("devtools.debugger.ui.panes-workers-and-sources-width", preferredWsw); + Services.prefs.setIntPref("devtools.debugger.ui.panes-instruments-width", preferredIw); + + finish(); + }); + } +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-01.js new file mode 100644 index 000000000..8481e2d5f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-01.js @@ -0,0 +1,33 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that simple JS can be parsed and cached with the reflection API. + */ + +function test() { + let { Parser } = Cu.import("resource://devtools/shared/Parser.jsm", {}); + + let source = "let x = 42;"; + let parser = new Parser(); + let first = parser.get(source); + let second = parser.get(source); + + isnot(first, second, + "The two syntax trees should be different."); + + let third = parser.get(source, "url"); + let fourth = parser.get(source, "url"); + + isnot(first, third, + "The new syntax trees should be different than the old ones."); + is(third, fourth, + "The new syntax trees were cached once an identifier was specified."); + + is(parser.errors.length, 0, + "There should be no errors logged when parsing."); + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-02.js new file mode 100644 index 000000000..6cf41b380 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-02.js @@ -0,0 +1,30 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that syntax errors are reported correctly. + */ + +function test() { + let { Parser } = Cu.import("resource://devtools/shared/Parser.jsm", {}); + + let source = "let x + 42;"; + let parser = new Parser(); + // Don't pollute the logs with exceptions that we are going to check anyhow. + parser.logExceptions = false; + let parsed = parser.get(source); + + ok(parsed, + "An object should be returned even though the source had a syntax error."); + + is(parser.errors.length, 1, + "There should be one error logged when parsing."); + is(parser.errors[0].name, "SyntaxError", + "The correct exception was caught."); + is(parser.errors[0].message, "missing ; before statement", + "The correct exception was caught."); + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-03.js new file mode 100644 index 000000000..439df705b --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-03.js @@ -0,0 +1,79 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that JS inside HTML can be separated and parsed correctly. + */ + +function test() { + let { Parser } = Cu.import("resource://devtools/shared/Parser.jsm", {}); + + let source = [ + "<!doctype html>", + "<head>", + "<script>", + "let a = 42;", + "</script>", + "<script type='text/javascript'>", + "let b = 42;", + "</script>", + "<script type='text/javascript;version=1.8'>", + "let c = 42;", + "</script>", + "</head>" + ].join("\n"); + let parser = new Parser(); + let parsed = parser.get(source); + + ok(parsed, + "HTML code should be parsed correctly."); + is(parser.errors.length, 0, + "There should be no errors logged when parsing."); + + is(parsed.scriptCount, 3, + "There should be 3 scripts parsed in the parent HTML source."); + + is(parsed.getScriptInfo(0).toSource(), "({start:-1, length:-1, index:-1})", + "There is no script at the beginning of the parent source."); + is(parsed.getScriptInfo(source.length - 1).toSource(), "({start:-1, length:-1, index:-1})", + "There is no script at the end of the parent source."); + + is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:31, length:13, index:0})", + "The first script was located correctly."); + is(parsed.getScriptInfo(source.indexOf("let b")).toSource(), "({start:85, length:13, index:1})", + "The second script was located correctly."); + is(parsed.getScriptInfo(source.indexOf("let c")).toSource(), "({start:151, length:13, index:2})", + "The third script was located correctly."); + + is(parsed.getScriptInfo(source.indexOf("let a") - 1).toSource(), "({start:31, length:13, index:0})", + "The left edge of the first script was interpreted correctly."); + is(parsed.getScriptInfo(source.indexOf("let b") - 1).toSource(), "({start:85, length:13, index:1})", + "The left edge of the second script was interpreted correctly."); + is(parsed.getScriptInfo(source.indexOf("let c") - 1).toSource(), "({start:151, length:13, index:2})", + "The left edge of the third script was interpreted correctly."); + + is(parsed.getScriptInfo(source.indexOf("let a") - 2).toSource(), "({start:-1, length:-1, index:-1})", + "The left outside of the first script was interpreted correctly."); + is(parsed.getScriptInfo(source.indexOf("let b") - 2).toSource(), "({start:-1, length:-1, index:-1})", + "The left outside of the second script was interpreted correctly."); + is(parsed.getScriptInfo(source.indexOf("let c") - 2).toSource(), "({start:-1, length:-1, index:-1})", + "The left outside of the third script was interpreted correctly."); + + is(parsed.getScriptInfo(source.indexOf("let a") + 12).toSource(), "({start:31, length:13, index:0})", + "The right edge of the first script was interpreted correctly."); + is(parsed.getScriptInfo(source.indexOf("let b") + 12).toSource(), "({start:85, length:13, index:1})", + "The right edge of the second script was interpreted correctly."); + is(parsed.getScriptInfo(source.indexOf("let c") + 12).toSource(), "({start:151, length:13, index:2})", + "The right edge of the third script was interpreted correctly."); + + is(parsed.getScriptInfo(source.indexOf("let a") + 13).toSource(), "({start:-1, length:-1, index:-1})", + "The right outside of the first script was interpreted correctly."); + is(parsed.getScriptInfo(source.indexOf("let b") + 13).toSource(), "({start:-1, length:-1, index:-1})", + "The right outside of the second script was interpreted correctly."); + is(parsed.getScriptInfo(source.indexOf("let c") + 13).toSource(), "({start:-1, length:-1, index:-1})", + "The right outside of the third script was interpreted correctly."); + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-04.js new file mode 100644 index 000000000..b6ae0dfb6 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-04.js @@ -0,0 +1,58 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that faulty JS inside HTML can be separated and identified correctly. + */ + +function test() { + let { Parser } = Cu.import("resource://devtools/shared/Parser.jsm", {}); + + let source = [ + "<!doctype html>", + "<head>", + "<SCRIPT>", + "let a + 42;", + "</SCRIPT>", + "<script type='text/javascript'>", + "let b = 42;", + "</SCRIPT>", + "<script type='text/javascript;version=1.8'>", + "let c + 42;", + "</SCRIPT>", + "</head>" + ].join("\n"); + let parser = new Parser(); + // Don't pollute the logs with exceptions that we are going to check anyhow. + parser.logExceptions = false; + let parsed = parser.get(source); + + ok(parsed, + "HTML code should be parsed correctly."); + is(parser.errors.length, 2, + "There should be two errors logged when parsing."); + + is(parser.errors[0].name, "SyntaxError", + "The correct first exception was caught."); + is(parser.errors[0].message, "missing ; before statement", + "The correct first exception was caught."); + + is(parser.errors[1].name, "SyntaxError", + "The correct second exception was caught."); + is(parser.errors[1].message, "missing ; before statement", + "The correct second exception was caught."); + + is(parsed.scriptCount, 1, + "There should be 1 script parsed in the parent HTML source."); + + is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:-1, length:-1, index:-1})", + "The first script shouldn't be considered valid."); + is(parsed.getScriptInfo(source.indexOf("let b")).toSource(), "({start:85, length:13, index:0})", + "The second script was located correctly."); + is(parsed.getScriptInfo(source.indexOf("let c")).toSource(), "({start:-1, length:-1, index:-1})", + "The third script shouldn't be considered valid."); + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-05.js new file mode 100644 index 000000000..b34d3952a --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-05.js @@ -0,0 +1,45 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that JS code containing strings that might look like <script> tags + * inside an HTML source is parsed correctly. + */ + +function test() { + let { Parser } = Cu.import("resource://devtools/shared/Parser.jsm", {}); + + let source = [ + "let a = [];", + "a.push('<script>');", + "a.push('var a = 42;');", + "a.push('</script>');", + "a.push('<script type=\"text/javascript\">');", + "a.push('var b = 42;');", + "a.push('</script>');", + "a.push('<script type=\"text/javascript;version=1.8\">');", + "a.push('var c = 42;');", + "a.push('</script>');" + ].join("\n"); + let parser = new Parser(); + let parsed = parser.get(source); + + ok(parsed, + "The javascript code should be parsed correctly."); + is(parser.errors.length, 0, + "There should be no errors logged when parsing."); + + is(parsed.scriptCount, 1, + "There should be 1 script parsed in the parent source."); + + is(parsed.getScriptInfo(source.indexOf("let a")).toSource(), "({start:0, length:261, index:0})", + "The script location is correct (1)."); + is(parsed.getScriptInfo(source.indexOf("<script>")).toSource(), "({start:0, length:261, index:0})", + "The script location is correct (2)."); + is(parsed.getScriptInfo(source.indexOf("</script>")).toSource(), "({start:0, length:261, index:0})", + "The script location is correct (3)."); + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-06.js new file mode 100644 index 000000000..4e5583e00 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-06.js @@ -0,0 +1,80 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that some potentially problematic identifier nodes have the + * right location information attached. + */ + +function test() { + let { Parser, ParserHelpers, SyntaxTreeVisitor } = + Cu.import("resource://devtools/shared/Parser.jsm", {}); + + function verify(source, predicate, [sline, scol], [eline, ecol]) { + let ast = Parser.reflectionAPI.parse(source); + let node = SyntaxTreeVisitor.filter(ast, predicate).pop(); + let loc = ParserHelpers.getNodeLocation(node); + + is(loc.start.toSource(), { line: sline, column: scol }.toSource(), + "The start location was correct for the identifier in: '" + source + "'."); + is(loc.end.toSource(), { line: eline, column: ecol }.toSource(), + "The end location was correct for the identifier in: '" + source + "'."); + } + + // FunctionDeclarations and FunctionExpressions. + + // The location is unavailable for the identifier node "foo". + verify("function foo(){}", e => e.name == "foo", [1, 9], [1, 12]); + verify("\nfunction\nfoo\n(\n)\n{\n}\n", e => e.name == "foo", [3, 0], [3, 3]); + + verify("({bar:function foo(){}})", e => e.name == "foo", [1, 15], [1, 18]); + verify("(\n{\nbar\n:\nfunction\nfoo\n(\n)\n{\n}\n}\n)", e => e.name == "foo", [6, 0], [6, 3]); + + // Just to be sure, check the identifier node "bar" as well. + verify("({bar:function foo(){}})", e => e.name == "bar", [1, 2], [1, 5]); + verify("(\n{\nbar\n:\nfunction\nfoo\n(\n)\n{\n}\n}\n)", e => e.name == "bar", [3, 0], [3, 3]); + + // MemberExpressions. + + // The location is unavailable for the identifier node "bar". + verify("foo.bar", e => e.name == "bar", [1, 4], [1, 7]); + verify("\nfoo\n.\nbar\n", e => e.name == "bar", [4, 0], [4, 3]); + + // Just to be sure, check the identifier node "foo" as well. + verify("foo.bar", e => e.name == "foo", [1, 0], [1, 3]); + verify("\nfoo\n.\nbar\n", e => e.name == "foo", [2, 0], [2, 3]); + + // VariableDeclarator + + // The location is incorrect for the identifier node "foo". + verify("let foo = bar", e => e.name == "foo", [1, 4], [1, 7]); + verify("\nlet\nfoo\n=\nbar\n", e => e.name == "foo", [3, 0], [3, 3]); + + // Just to be sure, check the identifier node "bar" as well. + verify("let foo = bar", e => e.name == "bar", [1, 10], [1, 13]); + verify("\nlet\nfoo\n=\nbar\n", e => e.name == "bar", [5, 0], [5, 3]); + + // Just to be sure, check AssignmentExpreesions as well. + verify("foo = bar", e => e.name == "foo", [1, 0], [1, 3]); + verify("\nfoo\n=\nbar\n", e => e.name == "foo", [2, 0], [2, 3]); + verify("foo = bar", e => e.name == "bar", [1, 6], [1, 9]); + verify("\nfoo\n=\nbar\n", e => e.name == "bar", [4, 0], [4, 3]); + + // LabeledStatement, BreakStatement and ContinueStatement, because it's 1968 again + + verify("foo: bar", e => e.name == "foo", [1, 0], [1, 3]); + verify("\nfoo\n:\nbar\n", e => e.name == "foo", [2, 0], [2, 3]); + + verify("foo: for(;;) break foo", e => e.name == "foo", [1, 19], [1, 22]); + verify("\nfoo\n:\nfor(\n;\n;\n)\nbreak\nfoo\n", e => e.name == "foo", [9, 0], [9, 3]); + + verify("foo: bar", e => e.name == "foo", [1, 0], [1, 3]); + verify("\nfoo\n:\nbar\n", e => e.name == "foo", [2, 0], [2, 3]); + + verify("foo: for(;;) continue foo", e => e.name == "foo", [1, 22], [1, 25]); + verify("\nfoo\n:\nfor(\n;\n;\n)\ncontinue\nfoo\n", e => e.name == "foo", [9, 0], [9, 3]); + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-07.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-07.js new file mode 100644 index 000000000..bea913a9e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-07.js @@ -0,0 +1,57 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that nodes with locaiton information attached can be properly + * verified for containing lines and columns. + */ + +function test() { + let { ParserHelpers } = Cu.import("resource://devtools/shared/Parser.jsm", {}); + + let node1 = { loc: { + start: { line: 1, column: 10 }, + end: { line: 10, column: 1 } + }}; + let node2 = { loc: { + start: { line: 1, column: 10 }, + end: { line: 1, column: 20 } + }}; + + ok(ParserHelpers.nodeContainsLine(node1, 1), "1st check."); + ok(ParserHelpers.nodeContainsLine(node1, 5), "2nd check."); + ok(ParserHelpers.nodeContainsLine(node1, 10), "3rd check."); + + ok(!ParserHelpers.nodeContainsLine(node1, 0), "4th check."); + ok(!ParserHelpers.nodeContainsLine(node1, 11), "5th check."); + + ok(ParserHelpers.nodeContainsLine(node2, 1), "6th check."); + ok(!ParserHelpers.nodeContainsLine(node2, 0), "7th check."); + ok(!ParserHelpers.nodeContainsLine(node2, 2), "8th check."); + + ok(!ParserHelpers.nodeContainsPoint(node1, 1, 10), "9th check."); + ok(!ParserHelpers.nodeContainsPoint(node1, 10, 1), "10th check."); + + ok(!ParserHelpers.nodeContainsPoint(node1, 0, 10), "11th check."); + ok(!ParserHelpers.nodeContainsPoint(node1, 11, 1), "12th check."); + + ok(!ParserHelpers.nodeContainsPoint(node1, 1, 9), "13th check."); + ok(!ParserHelpers.nodeContainsPoint(node1, 10, 2), "14th check."); + + ok(ParserHelpers.nodeContainsPoint(node2, 1, 10), "15th check."); + ok(ParserHelpers.nodeContainsPoint(node2, 1, 15), "16th check."); + ok(ParserHelpers.nodeContainsPoint(node2, 1, 20), "17th check."); + + ok(!ParserHelpers.nodeContainsPoint(node2, 0, 10), "18th check."); + ok(!ParserHelpers.nodeContainsPoint(node2, 2, 20), "19th check."); + + ok(!ParserHelpers.nodeContainsPoint(node2, 0, 9), "20th check."); + ok(!ParserHelpers.nodeContainsPoint(node2, 2, 21), "21th check."); + + ok(!ParserHelpers.nodeContainsPoint(node2, 1, 9), "22th check."); + ok(!ParserHelpers.nodeContainsPoint(node2, 1, 21), "23th check."); + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-08.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-08.js new file mode 100644 index 000000000..624f3c293 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-08.js @@ -0,0 +1,291 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that inferring anonymous function information is done correctly. + */ + +function test() { + let { Parser, ParserHelpers, SyntaxTreeVisitor } = + Cu.import("resource://devtools/shared/Parser.jsm", {}); + + function verify(source, predicate, details) { + let { name, chain } = details; + let [[sline, scol], [eline, ecol]] = details.loc; + let ast = Parser.reflectionAPI.parse(source); + let node = SyntaxTreeVisitor.filter(ast, predicate).pop(); + let info = ParserHelpers.inferFunctionExpressionInfo(node); + + is(info.name, name, + "The function expression assignment property name is correct."); + is(chain ? info.chain.toSource() : info.chain, chain ? chain.toSource() : chain, + "The function expression assignment property chain is correct."); + is(info.loc.start.toSource(), { line: sline, column: scol }.toSource(), + "The start location was correct for the identifier in: '" + source + "'."); + is(info.loc.end.toSource(), { line: eline, column: ecol }.toSource(), + "The end location was correct for the identifier in: '" + source + "'."); + } + + // VariableDeclarator + + verify("var foo=function(){}", e => e.type == "FunctionExpression", { + name: "foo", + chain: null, + loc: [[1, 4], [1, 7]] + }); + verify("\nvar\nfoo\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression", { + name: "foo", + chain: null, + loc: [[3, 0], [3, 3]] + }); + + // AssignmentExpression + + verify("foo=function(){}", e => e.type == "FunctionExpression", + { name: "foo", chain: [], loc: [[1, 0], [1, 3]] }); + + verify("\nfoo\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression", + { name: "foo", chain: [], loc: [[2, 0], [2, 3]] }); + + verify("foo.bar=function(){}", e => e.type == "FunctionExpression", + { name: "bar", chain: ["foo"], loc: [[1, 0], [1, 7]] }); + + verify("\nfoo.bar\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression", + { name: "bar", chain: ["foo"], loc: [[2, 0], [2, 7]] }); + + verify("this.foo=function(){}", e => e.type == "FunctionExpression", + { name: "foo", chain: ["this"], loc: [[1, 0], [1, 8]] }); + + verify("\nthis.foo\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression", + { name: "foo", chain: ["this"], loc: [[2, 0], [2, 8]] }); + + verify("this.foo.bar=function(){}", e => e.type == "FunctionExpression", + { name: "bar", chain: ["this", "foo"], loc: [[1, 0], [1, 12]] }); + + verify("\nthis.foo.bar\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression", + { name: "bar", chain: ["this", "foo"], loc: [[2, 0], [2, 12]] }); + + verify("foo.this.bar=function(){}", e => e.type == "FunctionExpression", + { name: "bar", chain: ["foo", "this"], loc: [[1, 0], [1, 12]] }); + + verify("\nfoo.this.bar\n=\nfunction\n(\n)\n{\n}\n", e => e.type == "FunctionExpression", + { name: "bar", chain: ["foo", "this"], loc: [[2, 0], [2, 12]] }); + + // ObjectExpression + + verify("({foo:function(){}})", e => e.type == "FunctionExpression", + { name: "foo", chain: [], loc: [[1, 2], [1, 5]] }); + + verify("(\n{\nfoo\n:\nfunction\n(\n)\n{\n}\n}\n)", e => e.type == "FunctionExpression", + { name: "foo", chain: [], loc: [[3, 0], [3, 3]] }); + + verify("({foo:{bar:function(){}}})", e => e.type == "FunctionExpression", + { name: "bar", chain: ["foo"], loc: [[1, 7], [1, 10]] }); + + verify("(\n{\nfoo\n:\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n}\n)", e => e.type == "FunctionExpression", + { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] }); + + // AssignmentExpression + ObjectExpression + + verify("foo={bar:function(){}}", e => e.type == "FunctionExpression", + { name: "bar", chain: ["foo"], loc: [[1, 5], [1, 8]] }); + + verify("\nfoo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression", + { name: "bar", chain: ["foo"], loc: [[5, 0], [5, 3]] }); + + verify("foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression", + { name: "baz", chain: ["foo", "bar"], loc: [[1, 10], [1, 13]] }); + + verify("\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["foo", "bar"], loc: [[8, 0], [8, 3]] }); + + verify("nested.foo={bar:function(){}}", e => e.type == "FunctionExpression", + { name: "bar", chain: ["nested", "foo"], loc: [[1, 12], [1, 15]] }); + + verify("\nnested.foo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression", + { name: "bar", chain: ["nested", "foo"], loc: [[5, 0], [5, 3]] }); + + verify("nested.foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression", + { name: "baz", chain: ["nested", "foo", "bar"], loc: [[1, 17], [1, 20]] }); + + verify("\nnested.foo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["nested", "foo", "bar"], loc: [[8, 0], [8, 3]] }); + + verify("this.foo={bar:function(){}}", e => e.type == "FunctionExpression", + { name: "bar", chain: ["this", "foo"], loc: [[1, 10], [1, 13]] }); + + verify("\nthis.foo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression", + { name: "bar", chain: ["this", "foo"], loc: [[5, 0], [5, 3]] }); + + verify("this.foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression", + { name: "baz", chain: ["this", "foo", "bar"], loc: [[1, 15], [1, 18]] }); + + verify("\nthis.foo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["this", "foo", "bar"], loc: [[8, 0], [8, 3]] }); + + verify("this.nested.foo={bar:function(){}}", e => e.type == "FunctionExpression", + { name: "bar", chain: ["this", "nested", "foo"], loc: [[1, 17], [1, 20]] }); + + verify("\nthis.nested.foo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression", + { name: "bar", chain: ["this", "nested", "foo"], loc: [[5, 0], [5, 3]] }); + + verify("this.nested.foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression", + { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[1, 22], [1, 25]] }); + + verify("\nthis.nested.foo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[8, 0], [8, 3]] }); + + verify("nested.this.foo={bar:function(){}}", e => e.type == "FunctionExpression", + { name: "bar", chain: ["nested", "this", "foo"], loc: [[1, 17], [1, 20]] }); + + verify("\nnested.this.foo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression", + { name: "bar", chain: ["nested", "this", "foo"], loc: [[5, 0], [5, 3]] }); + + verify("nested.this.foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression", + { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[1, 22], [1, 25]] }); + + verify("\nnested.this.foo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[8, 0], [8, 3]] }); + + // VariableDeclarator + AssignmentExpression + ObjectExpression + + verify("let foo={bar:function(){}}", e => e.type == "FunctionExpression", + { name: "bar", chain: ["foo"], loc: [[1, 9], [1, 12]] }); + + verify("\nlet\nfoo\n=\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n", e => e.type == "FunctionExpression", + { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] }); + + verify("let foo={bar:{baz:function(){}}}", e => e.type == "FunctionExpression", + { name: "baz", chain: ["foo", "bar"], loc: [[1, 14], [1, 17]] }); + + verify("\nlet\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["foo", "bar"], loc: [[9, 0], [9, 3]] }); + + // New/CallExpression + AssignmentExpression + ObjectExpression + + verify("foo({bar:function(){}})", e => e.type == "FunctionExpression", + { name: "bar", chain: [], loc: [[1, 5], [1, 8]] }); + + verify("\nfoo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); + + verify("foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression", + { name: "baz", chain: ["bar"], loc: [[1, 10], [1, 13]] }); + + verify("\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); + + verify("nested.foo({bar:function(){}})", e => e.type == "FunctionExpression", + { name: "bar", chain: [], loc: [[1, 12], [1, 15]] }); + + verify("\nnested.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); + + verify("nested.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression", + { name: "baz", chain: ["bar"], loc: [[1, 17], [1, 20]] }); + + verify("\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); + + verify("this.foo({bar:function(){}})", e => e.type == "FunctionExpression", + { name: "bar", chain: [], loc: [[1, 10], [1, 13]] }); + + verify("\nthis.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); + + verify("this.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression", + { name: "baz", chain: ["bar"], loc: [[1, 15], [1, 18]] }); + + verify("\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); + + verify("this.nested.foo({bar:function(){}})", e => e.type == "FunctionExpression", + { name: "bar", chain: [], loc: [[1, 17], [1, 20]] }); + + verify("\nthis.nested.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); + + verify("this.nested.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression", + { name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] }); + + verify("\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); + + verify("nested.this.foo({bar:function(){}})", e => e.type == "FunctionExpression", + { name: "bar", chain: [], loc: [[1, 17], [1, 20]] }); + + verify("\nnested.this.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); + + verify("nested.this.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression", + { name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] }); + + verify("\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); + + // New/CallExpression + VariableDeclarator + AssignmentExpression + ObjectExpression + + verify("let target=foo({bar:function(){}})", e => e.type == "FunctionExpression", + { name: "bar", chain: ["target"], loc: [[1, 16], [1, 19]] }); + + verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); + + verify("let target=foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[1, 21], [1, 24]] }); + + verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); + + verify("let target=nested.foo({bar:function(){}})", e => e.type == "FunctionExpression", + { name: "bar", chain: ["target"], loc: [[1, 23], [1, 26]] }); + + verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); + + verify("let target=nested.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[1, 28], [1, 31]] }); + + verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); + + verify("let target=this.foo({bar:function(){}})", e => e.type == "FunctionExpression", + { name: "bar", chain: ["target"], loc: [[1, 21], [1, 24]] }); + + verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); + + verify("let target=this.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[1, 26], [1, 29]] }); + + verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); + + verify("let target=this.nested.foo({bar:function(){}})", e => e.type == "FunctionExpression", + { name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] }); + + verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); + + verify("let target=this.nested.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] }); + + verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); + + verify("let target=nested.this.foo({bar:function(){}})", e => e.type == "FunctionExpression", + { name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] }); + + verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\nfunction\n(\n)\n{\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); + + verify("let target=nested.this.foo({bar:{baz:function(){}}})", e => e.type == "FunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] }); + + verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\nfunction\n(\n)\n{\n}\n}\n}\n)\n", e => e.type == "FunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-09.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-09.js new file mode 100644 index 000000000..2e0ac3b89 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-09.js @@ -0,0 +1,292 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that inferring anonymous function information is done correctly + * from arrow expressions. + */ + +function test() { + let { Parser, ParserHelpers, SyntaxTreeVisitor } = + Cu.import("resource://devtools/shared/Parser.jsm", {}); + + function verify(source, predicate, details) { + let { name, chain } = details; + let [[sline, scol], [eline, ecol]] = details.loc; + let ast = Parser.reflectionAPI.parse(source); + let node = SyntaxTreeVisitor.filter(ast, predicate).pop(); + let info = ParserHelpers.inferFunctionExpressionInfo(node); + + is(info.name, name, + "The function expression assignment property name is correct."); + is(chain ? info.chain.toSource() : info.chain, chain ? chain.toSource() : chain, + "The function expression assignment property chain is correct."); + is(info.loc.start.toSource(), { line: sline, column: scol }.toSource(), + "The start location was correct for the identifier in: '" + source + "'."); + is(info.loc.end.toSource(), { line: eline, column: ecol }.toSource(), + "The end location was correct for the identifier in: '" + source + "'."); + } + + // VariableDeclarator + + verify("var foo=()=>{}", e => e.type == "ArrowFunctionExpression", { + name: "foo", + chain: null, + loc: [[1, 4], [1, 7]] + }); + verify("\nvar\nfoo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", { + name: "foo", + chain: null, + loc: [[3, 0], [3, 3]] + }); + + // AssignmentExpression + + verify("foo=()=>{}", e => e.type == "ArrowFunctionExpression", + { name: "foo", chain: [], loc: [[1, 0], [1, 3]] }); + + verify("\nfoo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "foo", chain: [], loc: [[2, 0], [2, 3]] }); + + verify("foo.bar=()=>{}", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["foo"], loc: [[1, 0], [1, 7]] }); + + verify("\nfoo.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["foo"], loc: [[2, 0], [2, 7]] }); + + verify("this.foo=()=>{}", e => e.type == "ArrowFunctionExpression", + { name: "foo", chain: ["this"], loc: [[1, 0], [1, 8]] }); + + verify("\nthis.foo\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "foo", chain: ["this"], loc: [[2, 0], [2, 8]] }); + + verify("this.foo.bar=()=>{}", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["this", "foo"], loc: [[1, 0], [1, 12]] }); + + verify("\nthis.foo.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["this", "foo"], loc: [[2, 0], [2, 12]] }); + + verify("foo.this.bar=()=>{}", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["foo", "this"], loc: [[1, 0], [1, 12]] }); + + verify("\nfoo.this.bar\n=\n(\n)=>\n{\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["foo", "this"], loc: [[2, 0], [2, 12]] }); + + // ObjectExpression + + verify("({foo:()=>{}})", e => e.type == "ArrowFunctionExpression", + { name: "foo", chain: [], loc: [[1, 2], [1, 5]] }); + + verify("(\n{\nfoo\n:\n(\n)=>\n{\n}\n}\n)", e => e.type == "ArrowFunctionExpression", + { name: "foo", chain: [], loc: [[3, 0], [3, 3]] }); + + verify("({foo:{bar:()=>{}}})", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["foo"], loc: [[1, 7], [1, 10]] }); + + verify("(\n{\nfoo\n:\n{\nbar\n:\n(\n)=>\n{\n}\n}\n}\n)", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] }); + + // AssignmentExpression + ObjectExpression + + verify("foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["foo"], loc: [[1, 5], [1, 8]] }); + + verify("\nfoo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["foo"], loc: [[5, 0], [5, 3]] }); + + verify("foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["foo", "bar"], loc: [[1, 10], [1, 13]] }); + + verify("\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["foo", "bar"], loc: [[8, 0], [8, 3]] }); + + verify("nested.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["nested", "foo"], loc: [[1, 12], [1, 15]] }); + + verify("\nnested.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["nested", "foo"], loc: [[5, 0], [5, 3]] }); + + verify("nested.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["nested", "foo", "bar"], loc: [[1, 17], [1, 20]] }); + + verify("\nnested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["nested", "foo", "bar"], loc: [[8, 0], [8, 3]] }); + + verify("this.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["this", "foo"], loc: [[1, 10], [1, 13]] }); + + verify("\nthis.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["this", "foo"], loc: [[5, 0], [5, 3]] }); + + verify("this.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["this", "foo", "bar"], loc: [[1, 15], [1, 18]] }); + + verify("\nthis.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["this", "foo", "bar"], loc: [[8, 0], [8, 3]] }); + + verify("this.nested.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["this", "nested", "foo"], loc: [[1, 17], [1, 20]] }); + + verify("\nthis.nested.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["this", "nested", "foo"], loc: [[5, 0], [5, 3]] }); + + verify("this.nested.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[1, 22], [1, 25]] }); + + verify("\nthis.nested.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["this", "nested", "foo", "bar"], loc: [[8, 0], [8, 3]] }); + + verify("nested.this.foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["nested", "this", "foo"], loc: [[1, 17], [1, 20]] }); + + verify("\nnested.this.foo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["nested", "this", "foo"], loc: [[5, 0], [5, 3]] }); + + verify("nested.this.foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[1, 22], [1, 25]] }); + + verify("\nnested.this.foo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["nested", "this", "foo", "bar"], loc: [[8, 0], [8, 3]] }); + + // VariableDeclarator + AssignmentExpression + ObjectExpression + + verify("let foo={bar:()=>{}}", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["foo"], loc: [[1, 9], [1, 12]] }); + + verify("\nlet\nfoo\n=\n{\nbar\n:\n(\n)=>\n{\n}\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["foo"], loc: [[6, 0], [6, 3]] }); + + verify("let foo={bar:{baz:()=>{}}}", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["foo", "bar"], loc: [[1, 14], [1, 17]] }); + + verify("\nlet\nfoo\n=\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["foo", "bar"], loc: [[9, 0], [9, 3]] }); + + // New/CallExpression + AssignmentExpression + ObjectExpression + + verify("foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: [], loc: [[1, 5], [1, 8]] }); + + verify("\nfoo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); + + verify("foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["bar"], loc: [[1, 10], [1, 13]] }); + + verify("\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); + + verify("nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: [], loc: [[1, 12], [1, 15]] }); + + verify("\nnested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); + + verify("nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["bar"], loc: [[1, 17], [1, 20]] }); + + verify("\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); + + verify("this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: [], loc: [[1, 10], [1, 13]] }); + + verify("\nthis.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); + + verify("this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["bar"], loc: [[1, 15], [1, 18]] }); + + verify("\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); + + verify("this.nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: [], loc: [[1, 17], [1, 20]] }); + + verify("\nthis.nested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); + + verify("this.nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] }); + + verify("\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); + + verify("nested.this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: [], loc: [[1, 17], [1, 20]] }); + + verify("\nnested.this.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: [], loc: [[5, 0], [5, 3]] }); + + verify("nested.this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["bar"], loc: [[1, 22], [1, 25]] }); + + verify("\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["bar"], loc: [[8, 0], [8, 3]] }); + + // New/CallExpression + VariableDeclarator + AssignmentExpression + ObjectExpression + + verify("let target=foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["target"], loc: [[1, 16], [1, 19]] }); + + verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); + + verify("let target=foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[1, 21], [1, 24]] }); + + verify("\nlet\ntarget=\nfoo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); + + verify("let target=nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["target"], loc: [[1, 23], [1, 26]] }); + + verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); + + verify("let target=nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[1, 28], [1, 31]] }); + + verify("\nlet\ntarget=\nnested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); + + verify("let target=this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["target"], loc: [[1, 21], [1, 24]] }); + + verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); + + verify("let target=this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[1, 26], [1, 29]] }); + + verify("\nlet\ntarget=\nthis.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); + + verify("let target=this.nested.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] }); + + verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); + + verify("let target=this.nested.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] }); + + verify("\nlet\ntarget=\nthis.nested.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); + + verify("let target=nested.this.foo({bar:()=>{}})", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["target"], loc: [[1, 28], [1, 31]] }); + + verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n(\n)=>\n{\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "bar", chain: ["target"], loc: [[7, 0], [7, 3]] }); + + verify("let target=nested.this.foo({bar:{baz:()=>{}}})", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[1, 33], [1, 36]] }); + + verify("\nlet\ntarget=\nnested.this.foo\n(\n{\nbar\n:\n{\nbaz\n:\n(\n)=>\n{\n}\n}\n}\n)\n", e => e.type == "ArrowFunctionExpression", + { name: "baz", chain: ["target", "bar"], loc: [[10, 0], [10, 3]] }); + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-10.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-10.js new file mode 100644 index 000000000..d340f9f9c --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-10.js @@ -0,0 +1,129 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that creating an evaluation string for certain nodes works properly. + */ + +function test() { + let { Parser, ParserHelpers, SyntaxTreeVisitor } = + Cu.import("resource://devtools/shared/Parser.jsm", {}); + + function verify(source, predicate, string) { + let ast = Parser.reflectionAPI.parse(source); + let node = SyntaxTreeVisitor.filter(ast, predicate).pop(); + let info = ParserHelpers.getIdentifierEvalString(node); + is(info, string, "The identifier evaluation string is correct."); + } + + // Indentifier or Literal + + verify("foo", e => e.type == "Identifier", "foo"); + verify("undefined", e => e.type == "Identifier", "undefined"); + verify("null", e => e.type == "Literal", "null"); + verify("42", e => e.type == "Literal", "42"); + verify("true", e => e.type == "Literal", "true"); + verify("\"nasu\"", e => e.type == "Literal", "\"nasu\""); + + // MemberExpression or ThisExpression + + verify("this", e => e.type == "ThisExpression", "this"); + verify("foo.bar", e => e.name == "foo", "foo"); + verify("foo.bar", e => e.name == "bar", "foo.bar"); + + // MemberExpression + ThisExpression + + verify("this.foo.bar", e => e.type == "ThisExpression", "this"); + verify("this.foo.bar", e => e.name == "foo", "this.foo"); + verify("this.foo.bar", e => e.name == "bar", "this.foo.bar"); + + verify("foo.this.bar", e => e.name == "foo", "foo"); + verify("foo.this.bar", e => e.name == "this", "foo.this"); + verify("foo.this.bar", e => e.name == "bar", "foo.this.bar"); + + // ObjectExpression + VariableDeclarator + + verify("let foo={bar:baz}", e => e.name == "baz", "baz"); + verify("let foo={bar:undefined}", e => e.name == "undefined", "undefined"); + verify("let foo={bar:null}", e => e.type == "Literal", "null"); + verify("let foo={bar:42}", e => e.type == "Literal", "42"); + verify("let foo={bar:true}", e => e.type == "Literal", "true"); + verify("let foo={bar:\"nasu\"}", e => e.type == "Literal", "\"nasu\""); + verify("let foo={bar:this}", e => e.type == "ThisExpression", "this"); + + verify("let foo={bar:{nested:baz}}", e => e.name == "baz", "baz"); + verify("let foo={bar:{nested:undefined}}", e => e.name == "undefined", "undefined"); + verify("let foo={bar:{nested:null}}", e => e.type == "Literal", "null"); + verify("let foo={bar:{nested:42}}", e => e.type == "Literal", "42"); + verify("let foo={bar:{nested:true}}", e => e.type == "Literal", "true"); + verify("let foo={bar:{nested:\"nasu\"}}", e => e.type == "Literal", "\"nasu\""); + verify("let foo={bar:{nested:this}}", e => e.type == "ThisExpression", "this"); + + verify("let foo={bar:baz}", e => e.name == "bar", "foo.bar"); + verify("let foo={bar:baz}", e => e.name == "foo", "foo"); + + verify("let foo={bar:{nested:baz}}", e => e.name == "nested", "foo.bar.nested"); + verify("let foo={bar:{nested:baz}}", e => e.name == "bar", "foo.bar"); + verify("let foo={bar:{nested:baz}}", e => e.name == "foo", "foo"); + + // ObjectExpression + MemberExpression + + verify("parent.foo={bar:baz}", e => e.name == "bar", "parent.foo.bar"); + verify("parent.foo={bar:baz}", e => e.name == "foo", "parent.foo"); + verify("parent.foo={bar:baz}", e => e.name == "parent", "parent"); + + verify("parent.foo={bar:{nested:baz}}", e => e.name == "nested", "parent.foo.bar.nested"); + verify("parent.foo={bar:{nested:baz}}", e => e.name == "bar", "parent.foo.bar"); + verify("parent.foo={bar:{nested:baz}}", e => e.name == "foo", "parent.foo"); + verify("parent.foo={bar:{nested:baz}}", e => e.name == "parent", "parent"); + + verify("this.foo={bar:{nested:baz}}", e => e.name == "nested", "this.foo.bar.nested"); + verify("this.foo={bar:{nested:baz}}", e => e.name == "bar", "this.foo.bar"); + verify("this.foo={bar:{nested:baz}}", e => e.name == "foo", "this.foo"); + verify("this.foo={bar:{nested:baz}}", e => e.type == "ThisExpression", "this"); + + verify("this.parent.foo={bar:{nested:baz}}", e => e.name == "nested", "this.parent.foo.bar.nested"); + verify("this.parent.foo={bar:{nested:baz}}", e => e.name == "bar", "this.parent.foo.bar"); + verify("this.parent.foo={bar:{nested:baz}}", e => e.name == "foo", "this.parent.foo"); + verify("this.parent.foo={bar:{nested:baz}}", e => e.name == "parent", "this.parent"); + verify("this.parent.foo={bar:{nested:baz}}", e => e.type == "ThisExpression", "this"); + + verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "nested", "parent.this.foo.bar.nested"); + verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "bar", "parent.this.foo.bar"); + verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "foo", "parent.this.foo"); + verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "this", "parent.this"); + verify("parent.this.foo={bar:{nested:baz}}", e => e.name == "parent", "parent"); + + // FunctionExpression + + verify("function foo(){}", e => e.name == "foo", "foo"); + verify("var foo=function(){}", e => e.name == "foo", "foo"); + verify("var foo=function bar(){}", e => e.name == "bar", "bar"); + + // New/CallExpression + + verify("foo()", e => e.name == "foo", "foo"); + verify("new foo()", e => e.name == "foo", "foo"); + + verify("foo(bar)", e => e.name == "bar", "bar"); + verify("foo(bar, baz)", e => e.name == "baz", "baz"); + verify("foo(undefined)", e => e.name == "undefined", "undefined"); + verify("foo(null)", e => e.type == "Literal", "null"); + verify("foo(42)", e => e.type == "Literal", "42"); + verify("foo(true)", e => e.type == "Literal", "true"); + verify("foo(\"nasu\")", e => e.type == "Literal", "\"nasu\""); + verify("foo(this)", e => e.type == "ThisExpression", "this"); + + // New/CallExpression + ObjectExpression + MemberExpression + + verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "nested", "this.parent.foo.bar.nested"); + verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "bar", "this.parent.foo.bar"); + verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "foo", "this.parent.foo"); + verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "parent", "this.parent"); + verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.type == "ThisExpression", "this"); + verify("fun(this.parent.foo={bar:{nested:baz}})", e => e.name == "fun", "fun"); + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-11.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-11.js new file mode 100644 index 000000000..ee2b4c89d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-11.js @@ -0,0 +1,41 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Checks if self-closing <script/> tags are parsed by Parser.jsm + */ + +function test() { + let { Parser } = Cu.import("resource://devtools/shared/Parser.jsm", {}); + + let source = [ + '<script type="text/javascript" src="chrome://foo.js"/>', + '<script type="application/javascript;version=1.8" src="chrome://baz.js"/>', + '<script async defer src="chrome://foobar.js"/>', + '<script type="application/javascript"/>"hello third"', + '<script type="application/javascript">"hello fourth"</script>', + ].join("\n"); + let parser = new Parser(); + let parsed = parser.get(source); + + is(parser.errors.length, 0, + "There should be no errors logged when parsing."); + is(parsed.scriptCount, 5, + "There should be 5 scripts parsed in the parent HTML source."); + + is(parsed.getScriptInfo(source.indexOf("foo.js\"/>") + 1).toSource(), "({start:-1, length:-1, index:-1})", + "the first script is empty"); + is(parsed.getScriptInfo(source.indexOf("baz.js\"/>") + 1).toSource(), "({start:-1, length:-1, index:-1})", + "the second script is empty"); + is(parsed.getScriptInfo(source.indexOf("foobar.js\"/>") + 1).toSource(), "({start:-1, length:-1, index:-1})", + "the third script is empty"); + + is(parsed.getScriptInfo(source.indexOf("hello third!")).toSource(), "({start:-1, length:-1, index:-1})", + "Inline script on self-closing tag not considered a script"); + is(parsed.getScriptInfo(source.indexOf("hello fourth")).toSource(), "({start:267, length:14, index:4})", + "The fourth script was located correctly."); + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-computed-name.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-computed-name.js new file mode 100644 index 000000000..085f9781b --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-computed-name.js @@ -0,0 +1,32 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that template strings are correctly processed. + */ + +"use strict"; + +function test() { + let { Parser, SyntaxTreeVisitor } = + Cu.import("resource://devtools/shared/Parser.jsm", {}); + + let ast = Parser.reflectionAPI.parse("({ [i]: 1 })"); + let nodes = SyntaxTreeVisitor.filter(ast, e => e.type == "ComputedName"); + ok(nodes && nodes.length === 1, "Found the ComputedName node"); + + let name = nodes[0].name; + ok(name, "The ComputedName node has a name property"); + is(name.type, "Identifier", "The name has a correct type"); + is(name.name, "i", "The name has a correct name"); + + let identNodes = SyntaxTreeVisitor.filter(ast, e => e.type == "Identifier"); + ok(identNodes && identNodes.length === 1, "Found the Identifier node"); + + is(identNodes[0].type, "Identifier", "The identifier has a correct type"); + is(identNodes[0].name, "i", "The identifier has a correct name"); + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-function-defaults.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-function-defaults.js new file mode 100644 index 000000000..55fac4055 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-function-defaults.js @@ -0,0 +1,31 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that function default arguments are correctly processed. + */ + +"use strict"; + +function test() { + let { Parser, ParserHelpers, SyntaxTreeVisitor } = + Cu.import("resource://devtools/shared/Parser.jsm", {}); + + function verify(source, predicate, string) { + let ast = Parser.reflectionAPI.parse(source); + let node = SyntaxTreeVisitor.filter(ast, predicate).pop(); + let info = ParserHelpers.getIdentifierEvalString(node); + is(info, string, "The identifier evaluation string is correct."); + } + + // FunctionDeclaration + verify("function foo(a, b='b') {}", e => e.type == "Literal", "\"b\""); + // FunctionExpression + verify("let foo=function(a, b='b') {}", e => e.type == "Literal", "\"b\""); + // ArrowFunctionExpression + verify("let foo=(a, b='b')=> {}", e => e.type == "Literal", "\"b\""); + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-spread-expression.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-spread-expression.js new file mode 100644 index 000000000..4b7d93d2f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-spread-expression.js @@ -0,0 +1,32 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that spread expressions work both in arrays and function calls. + */ + +"use strict"; + +function test() { + let { Parser, SyntaxTreeVisitor } = + Cu.import("resource://devtools/shared/Parser.jsm", {}); + + const SCRIPTS = ["[...a]", "foo(...a)"]; + + for (let script of SCRIPTS) { + info(`Testing spread expression in '${script}'`); + let ast = Parser.reflectionAPI.parse(script); + let nodes = SyntaxTreeVisitor.filter(ast, + e => e.type == "SpreadExpression"); + ok(nodes && nodes.length === 1, "Found the SpreadExpression node"); + + let expr = nodes[0].expression; + ok(expr, "The SpreadExpression node has the sub-expression"); + is(expr.type, "Identifier", "The sub-expression is an Identifier"); + is(expr.name, "a", "The sub-expression identifier has a correct name"); + } + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_parser-template-strings.js b/devtools/client/debugger/test/mochitest/browser_dbg_parser-template-strings.js new file mode 100644 index 000000000..6ee271137 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_parser-template-strings.js @@ -0,0 +1,29 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that template strings are correctly processed. + */ + +"use strict"; + +function test() { + let { Parser, SyntaxTreeVisitor } = + Cu.import("resource://devtools/shared/Parser.jsm", {}); + + let ast = Parser.reflectionAPI.parse("`foo${i}bar`"); + let nodes = SyntaxTreeVisitor.filter(ast, e => e.type == "TemplateLiteral"); + ok(nodes && nodes.length === 1, "Found the TemplateLiteral node"); + + let elements = nodes[0].elements; + ok(elements, "The TemplateLiteral node has elements"); + is(elements.length, 3, "There are 3 elements in the literal"); + + ["Literal", "Identifier", "Literal"].forEach((type, i) => { + is(elements[i].type, type, `Element at index ${i} is '${type}'`); + }); + + finish(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-01.js new file mode 100644 index 000000000..92cd7e4bc --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-01.js @@ -0,0 +1,246 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that pausing on exceptions works. + */ + +const TAB_URL = EXAMPLE_URL + "doc_pause-exceptions.html"; + +var gTab, gPanel, gDebugger; +var gFrames, gVariables, gPrefs, gOptions; + +function test() { + requestLongerTimeout(2); + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gFrames = gDebugger.DebuggerView.StackFrames; + gVariables = gDebugger.DebuggerView.Variables; + gPrefs = gDebugger.Prefs; + gOptions = gDebugger.DebuggerView.Options; + + is(gPrefs.pauseOnExceptions, false, + "The pause-on-exceptions pref should be disabled by default."); + isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true", + "The pause-on-exceptions menu item should not be checked."); + + testPauseOnExceptionsDisabled() + .then(enablePauseOnExceptions) + .then(disableIgnoreCaughtExceptions) + .then(testPauseOnExceptionsEnabled) + .then(disablePauseOnExceptions) + .then(enableIgnoreCaughtExceptions) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testPauseOnExceptionsDisabled() { + let finished = waitForCaretAndScopes(gPanel, 26).then(() => { + info("Testing disabled pause-on-exceptions."); + + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused (1)."); + ok(isCaretPos(gPanel, 26), + "Should be paused on the debugger statement (1)."); + + let innerScope = gVariables.getScopeAtIndex(0); + let innerNodes = innerScope.target.querySelector(".variables-view-element-details").childNodes; + + is(gFrames.itemCount, 1, + "Should have one frame."); + is(gVariables._store.length, 4, + "Should have four scopes."); + + is(innerNodes[0].querySelector(".name").getAttribute("value"), "this", + "Should have the right property name for 'this'."); + is(innerNodes[0].querySelector(".value").getAttribute("value"), "<button>", + "Should have the right property value for 'this'."); + + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => { + isnot(gDebugger.gThreadClient.state, "paused", + "Should not be paused after resuming."); + ok(isCaretPos(gPanel, 26), + "Should be idle on the debugger statement."); + + ok(true, "Frames were cleared, debugger didn't pause again."); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + + return finished; + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + + return finished; +} + +function testPauseOnExceptionsEnabled() { + let finished = waitForCaretAndScopes(gPanel, 19).then(() => { + info("Testing enabled pause-on-exceptions."); + + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + ok(isCaretPos(gPanel, 19), + "Should be paused on the debugger statement."); + + let innerScope = gVariables.getScopeAtIndex(0); + let innerNodes = innerScope.target.querySelector(".variables-view-element-details").childNodes; + + is(gFrames.itemCount, 1, + "Should have one frame."); + is(gVariables._store.length, 4, + "Should have four scopes."); + + is(innerNodes[0].querySelector(".name").getAttribute("value"), "<exception>", + "Should have the right property name for <exception>."); + is(innerNodes[0].querySelector(".value").getAttribute("value"), "Error", + "Should have the right property value for <exception>."); + + let finished = waitForCaretAndScopes(gPanel, 26).then(() => { + info("Testing enabled pause-on-exceptions and resumed after pause."); + + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + ok(isCaretPos(gPanel, 26), + "Should be paused on the debugger statement."); + + let innerScope = gVariables.getScopeAtIndex(0); + let innerNodes = innerScope.target.querySelector(".variables-view-element-details").childNodes; + + is(gFrames.itemCount, 1, + "Should have one frame."); + is(gVariables._store.length, 4, + "Should have four scopes."); + + is(innerNodes[0].querySelector(".name").getAttribute("value"), "this", + "Should have the right property name for 'this'."); + is(innerNodes[0].querySelector(".value").getAttribute("value"), "<button>", + "Should have the right property value for 'this'."); + + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => { + isnot(gDebugger.gThreadClient.state, "paused", + "Should not be paused after resuming."); + ok(isCaretPos(gPanel, 26), + "Should be idle on the debugger statement."); + + ok(true, "Frames were cleared, debugger didn't pause again."); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + + return finished; + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + + return finished; + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + + return finished; +} + +function enablePauseOnExceptions() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + is(gPrefs.pauseOnExceptions, true, + "The pause-on-exceptions pref should now be enabled."); + is(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true", + "The pause-on-exceptions menu item should now be checked."); + + ok(true, "Pausing on exceptions was enabled."); + deferred.resolve(); + }); + + gOptions._pauseOnExceptionsItem.setAttribute("checked", "true"); + gOptions._togglePauseOnExceptions(); + + return deferred.promise; +} + +function disablePauseOnExceptions() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + is(gPrefs.pauseOnExceptions, false, + "The pause-on-exceptions pref should now be disabled."); + isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true", + "The pause-on-exceptions menu item should now be unchecked."); + + ok(true, "Pausing on exceptions was disabled."); + deferred.resolve(); + }); + + gOptions._pauseOnExceptionsItem.setAttribute("checked", "false"); + gOptions._togglePauseOnExceptions(); + + return deferred.promise; +} + +function enableIgnoreCaughtExceptions() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + is(gPrefs.ignoreCaughtExceptions, true, + "The ignore-caught-exceptions pref should now be enabled."); + is(gOptions._ignoreCaughtExceptionsItem.getAttribute("checked"), "true", + "The ignore-caught-exceptions menu item should now be checked."); + + ok(true, "Ignore caught exceptions was enabled."); + deferred.resolve(); + }); + + gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "true"); + gOptions._toggleIgnoreCaughtExceptions(); + + return deferred.promise; +} + +function disableIgnoreCaughtExceptions() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + is(gPrefs.ignoreCaughtExceptions, false, + "The ignore-caught-exceptions pref should now be disabled."); + isnot(gOptions._ignoreCaughtExceptionsItem.getAttribute("checked"), "true", + "The ignore-caught-exceptions menu item should now be unchecked."); + + ok(true, "Ignore caught exceptions was disabled."); + deferred.resolve(); + }); + + gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "false"); + gOptions._toggleIgnoreCaughtExceptions(); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gFrames = null; + gVariables = null; + gPrefs = null; + gOptions = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-02.js new file mode 100644 index 000000000..21b28ce26 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-02.js @@ -0,0 +1,204 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that pausing on exceptions works after reload. + */ + +const TAB_URL = EXAMPLE_URL + "doc_pause-exceptions.html"; + +var gTab, gPanel, gDebugger; +var gFrames, gVariables, gPrefs, gOptions; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gFrames = gDebugger.DebuggerView.StackFrames; + gVariables = gDebugger.DebuggerView.Variables; + gPrefs = gDebugger.Prefs; + gOptions = gDebugger.DebuggerView.Options; + + is(gPrefs.pauseOnExceptions, false, + "The pause-on-exceptions pref should be disabled by default."); + isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true", + "The pause-on-exceptions menu item should not be checked."); + + enablePauseOnExceptions() + .then(disableIgnoreCaughtExceptions) + .then(() => reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN)) + .then(() => { + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }) + .then(testPauseOnExceptionsAfterReload) + .then(disablePauseOnExceptions) + .then(enableIgnoreCaughtExceptions) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testPauseOnExceptionsAfterReload() { + let finished = waitForCaretAndScopes(gPanel, 19).then(() => { + info("Testing enabled pause-on-exceptions."); + + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + ok(isCaretPos(gPanel, 19), + "Should be paused on the debugger statement."); + + let innerScope = gVariables.getScopeAtIndex(0); + let innerNodes = innerScope.target.querySelector(".variables-view-element-details").childNodes; + + is(gFrames.itemCount, 1, + "Should have one frame."); + is(gVariables._store.length, 4, + "Should have four scopes."); + + is(innerNodes[0].querySelector(".name").getAttribute("value"), "<exception>", + "Should have the right property name for <exception>."); + is(innerNodes[0].querySelector(".value").getAttribute("value"), "Error", + "Should have the right property value for <exception>."); + + let finished = waitForCaretAndScopes(gPanel, 26).then(() => { + info("Testing enabled pause-on-exceptions and resumed after pause."); + + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + ok(isCaretPos(gPanel, 26), + "Should be paused on the debugger statement."); + + let innerScope = gVariables.getScopeAtIndex(0); + let innerNodes = innerScope.target.querySelector(".variables-view-element-details").childNodes; + + is(gFrames.itemCount, 1, + "Should have one frame."); + is(gVariables._store.length, 4, + "Should have four scopes."); + + is(innerNodes[0].querySelector(".name").getAttribute("value"), "this", + "Should have the right property name for 'this'."); + is(innerNodes[0].querySelector(".value").getAttribute("value"), "<button>", + "Should have the right property value for 'this'."); + + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => { + isnot(gDebugger.gThreadClient.state, "paused", + "Should not be paused after resuming."); + ok(isCaretPos(gPanel, 26), + "Should be idle on the debugger statement."); + + ok(true, "Frames were cleared, debugger didn't pause again."); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + + return finished; + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + + return finished; + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + + return finished; +} + +function enablePauseOnExceptions() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + is(gPrefs.pauseOnExceptions, true, + "The pause-on-exceptions pref should now be enabled."); + is(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true", + "The pause-on-exceptions menu item should now be checked."); + + ok(true, "Pausing on exceptions was enabled."); + deferred.resolve(); + }); + + gOptions._pauseOnExceptionsItem.setAttribute("checked", "true"); + gOptions._togglePauseOnExceptions(); + return deferred.promise; +} + +function disablePauseOnExceptions() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + is(gPrefs.pauseOnExceptions, false, + "The pause-on-exceptions pref should now be disabled."); + isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true", + "The pause-on-exceptions menu item should now be unchecked."); + + ok(true, "Pausing on exceptions was disabled."); + deferred.resolve(); + }); + + gOptions._pauseOnExceptionsItem.setAttribute("checked", "false"); + gOptions._togglePauseOnExceptions(); + + return deferred.promise; +} + +function enableIgnoreCaughtExceptions() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + is(gPrefs.ignoreCaughtExceptions, true, + "The ignore-caught-exceptions pref should now be enabled."); + is(gOptions._ignoreCaughtExceptionsItem.getAttribute("checked"), "true", + "The ignore-caught-exceptions menu item should now be checked."); + + ok(true, "Ignore caught exceptions was enabled."); + deferred.resolve(); + }); + + gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "true"); + gOptions._toggleIgnoreCaughtExceptions(); + + return deferred.promise; +} + +function disableIgnoreCaughtExceptions() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + is(gPrefs.ignoreCaughtExceptions, false, + "The ignore-caught-exceptions pref should now be disabled."); + isnot(gOptions._ignoreCaughtExceptionsItem.getAttribute("checked"), "true", + "The ignore-caught-exceptions menu item should now be unchecked."); + + ok(true, "Ignore caught exceptions was disabled."); + deferred.resolve(); + }); + + gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "false"); + gOptions._toggleIgnoreCaughtExceptions(); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gFrames = null; + gVariables = null; + gPrefs = null; + gOptions = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pause-no-step.js b/devtools/client/debugger/test/mochitest/browser_dbg_pause-no-step.js new file mode 100644 index 000000000..d4e8a05d2 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pause-no-step.js @@ -0,0 +1,94 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that pausing / stepping is only enabled when there is a + * location. + */ + +const TAB_URL = EXAMPLE_URL + "doc_pause-exceptions.html"; + +var gTab, gPanel, gDebugger; +var gResumeButton, gStepOverButton, gStepOutButton, gStepInButton; +var gResumeKey, gFrames; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gResumeButton = gDebugger.document.getElementById("resume"); + gStepOverButton = gDebugger.document.getElementById("step-over"); + gStepInButton = gDebugger.document.getElementById("step-in"); + gStepOutButton = gDebugger.document.getElementById("step-out"); + gResumeKey = gDebugger.document.getElementById("resumeKey"); + gFrames = gDebugger.DebuggerView.StackFrames; + + testPause(); + }); +} + +function testPause() { + ok(!gDebugger.gThreadClient.paused, "Should be running after starting the test."); + ok(gStepOutButton.disabled, "Stepping out button should be disabled"); + ok(gStepInButton.disabled, "Stepping in button should be disabled"); + ok(gStepOverButton.disabled, "Stepping over button should be disabled"); + + gDebugger.gThreadClient.addOneTimeListener("paused", () => { + ok(gDebugger.gThreadClient.paused, + "Should be paused after an interrupt request."); + + ok(!gStepOutButton.disabled, "Stepping out button should be enabled"); + ok(!gStepInButton.disabled, "Stepping in button should be enabled"); + ok(!gStepOverButton.disabled, "Stepping over button should be enabled"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED).then(() => { + is(gFrames.itemCount, 1, + "Should have 1 frame from the evalInTab call."); + gDebugger.gThreadClient.resume(testBreakAtLocation); + }); + + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger); + + ok(!gDebugger.gThreadClient.paused, + "Shouldn't be paused until the next script is executed."); + ok(gStepOutButton.disabled, "Stepping out button should be disabled"); + ok(gStepInButton.disabled, "Stepping in button should be disabled"); + ok(gStepOverButton.disabled, "Stepping over button should be disabled"); + + // Evaluate a script to fully pause the debugger + once(gDebugger.gClient, "willInterrupt").then(() => { + evalInTab(gTab, "1+1;"); + }); +} + +function testBreakAtLocation() { + gDebugger.gThreadClient.addOneTimeListener("paused", () => { + ok(!gStepOutButton.disabled, "Stepping out button should be enabled"); + ok(!gStepInButton.disabled, "Stepping in button should be enabled"); + ok(!gStepOverButton.disabled, "Stepping over button should be enabled"); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); + + BrowserTestUtils.synthesizeMouseAtCenter("button", {}, gBrowser.selectedBrowser); +} + +registerCleanupFunction(function () { + gPanel = null; + gDebugger = null; + gResumeButton = null; + gStepOverButton = null; + gStepInButton = null; + gStepOutButton = null; + gResumeKey = null; + gFrames = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pause-resume.js b/devtools/client/debugger/test/mochitest/browser_dbg_pause-resume.js new file mode 100644 index 000000000..e9aaebe55 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pause-resume.js @@ -0,0 +1,91 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if pausing and resuming in the main loop works properly. + */ + +const TAB_URL = EXAMPLE_URL + "doc_pause-exceptions.html"; + +var gTab, gPanel, gDebugger; +var gResumeButton, gResumeKey, gFrames; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gResumeButton = gDebugger.document.getElementById("resume"); + gResumeKey = gDebugger.document.getElementById("resumeKey"); + gFrames = gDebugger.DebuggerView.StackFrames; + + testPause(); + }); +} + +function testPause() { + is(gDebugger.gThreadClient.paused, false, + "Should be running after starting the test."); + + is(gResumeButton.getAttribute("tooltiptext"), + gDebugger.L10N.getFormatStr("pauseButtonTooltip", + gDebugger.ShortcutUtils.prettifyShortcut(gResumeKey)), + "Button tooltip should be 'pause' when running."); + + gDebugger.gThreadClient.addOneTimeListener("paused", () => { + is(gDebugger.gThreadClient.paused, true, + "Should be paused after an interrupt request."); + + is(gResumeButton.getAttribute("tooltiptext"), + gDebugger.L10N.getFormatStr("resumeButtonTooltip", + gDebugger.ShortcutUtils.prettifyShortcut(gResumeKey)), + "Button tooltip should be 'resume' when paused."); + + is(gFrames.itemCount, 0, + "Should have no frames when paused in the main loop."); + + testResume(); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger); + + is(gResumeButton.getAttribute("tooltiptext"), + gDebugger.L10N.getFormatStr("pausePendingButtonTooltip"), + "Button tooltip should be 'waiting for execution' when breaking on nex."); + + // Evaluate a script to fully pause the debugger + once(gDebugger.gClient, "willInterrupt").then(() => { + evalInTab(gTab, "1+1;"); + }); +} + +function testResume() { + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + is(gDebugger.gThreadClient.paused, false, + "Should be paused after an interrupt request."); + + is(gResumeButton.getAttribute("tooltiptext"), + gDebugger.L10N.getFormatStr("pauseButtonTooltip", + gDebugger.ShortcutUtils.prettifyShortcut(gResumeKey)), + "Button tooltip should be pause when running."); + + closeDebuggerAndFinish(gPanel); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gResumeButton = null; + gResumeKey = null; + gFrames = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pause-warning.js b/devtools/client/debugger/test/mochitest/browser_dbg_pause-warning.js new file mode 100644 index 000000000..bcd2599dc --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pause-warning.js @@ -0,0 +1,109 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if a warning is shown in the inspector when debugger is paused. + */ + +const TAB_URL = EXAMPLE_URL + "doc_inline-script.html"; + +var gTab, gPanel, gDebugger; +var gTarget, gToolbox; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gTarget = gPanel.target; + gToolbox = gPanel._toolbox; + + testPause(); + }); +} + +function testPause() { + gDebugger.gThreadClient.addOneTimeListener("paused", () => { + ok(gDebugger.gThreadClient.paused, + "threadClient.paused has been updated to true."); + + gToolbox.once("inspector-selected").then(inspector => { + inspector.once("inspector-updated").then(testNotificationIsUp1); + }); + gToolbox.selectTool("inspector"); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + + // Evaluate a script to fully pause the debugger + once(gDebugger.gClient, "willInterrupt").then(() => { + evalInTab(gTab, "1+1;"); + }); +} + +function testNotificationIsUp1() { + let notificationBox = gToolbox.getNotificationBox(); + let notification = notificationBox.getNotificationWithValue("inspector-script-paused"); + + ok(notification, + "Inspector notification is present (1)."); + + gToolbox.once("jsdebugger-selected", testNotificationIsHidden); + gToolbox.selectTool("jsdebugger"); +} + +function testNotificationIsHidden() { + let notificationBox = gToolbox.getNotificationBox(); + let notification = notificationBox.getNotificationWithValue("inspector-script-paused"); + + ok(!notification, + "Inspector notification is hidden (2)."); + + gToolbox.once("inspector-selected", testNotificationIsUp2); + gToolbox.selectTool("inspector"); +} + +function testNotificationIsUp2() { + let notificationBox = gToolbox.getNotificationBox(); + let notification = notificationBox.getNotificationWithValue("inspector-script-paused"); + + ok(notification, + "Inspector notification is present again (3)."); + + testResume(); +} + +function testResume() { + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + ok(!gDebugger.gThreadClient.paused, + "threadClient.paused has been updated to false."); + + let notificationBox = gToolbox.getNotificationBox(); + let notification = notificationBox.getNotificationWithValue("inspector-script-paused"); + + ok(!notification, + "Inspector notification was removed once debugger resumed."); + + closeDebuggerAndFinish(gPanel); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gTarget = null; + gToolbox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_paused-keybindings.js b/devtools/client/debugger/test/mochitest/browser_dbg_paused-keybindings.js new file mode 100644 index 000000000..ebffbdd9d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_paused-keybindings.js @@ -0,0 +1,50 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that keybindings still work when the content window is paused and +// the tab is selected again. + +function test() { + Task.spawn(function* () { + const TAB_URL = EXAMPLE_URL + "doc_inline-script.html"; + let gDebugger, searchBox; + + let options = { + source: TAB_URL, + line: 1 + }; + let [tab, debuggee, panel] = yield initDebugger(TAB_URL, options); + gDebugger = panel.panelWin; + searchBox = gDebugger.DebuggerView.Filtering._searchbox; + + let onCaretUpdated = ensureCaretAt(panel, 20, 1, true); + let onThreadPaused = ensureThreadClientState(panel, "paused"); + ContentTask.spawn(tab.linkedBrowser, {}, function* () { + content.document.querySelector("button").click(); + }); + yield onCaretUpdated; + yield onThreadPaused + + // Now open a tab and close it. + let tab2 = yield addTab(TAB_URL); + yield waitForTick(); + yield removeTab(tab2); + yield ensureCaretAt(panel, 20); + + // Try to use the Cmd-L keybinding to see if it still works. + let caretMove = ensureCaretAt(panel, 15, 1, true); + // Wait a tick for the editor focus event to occur first. + executeSoon(function () { + EventUtils.synthesizeKey("l", { accelKey: true }); + EventUtils.synthesizeKey("1", {}); + EventUtils.synthesizeKey("5", {}); + }); + yield caretMove; + + yield resumeDebuggerThenCloseAndFinish(panel); + }).then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_post-page.js b/devtools/client/debugger/test/mochitest/browser_dbg_post-page.js new file mode 100644 index 000000000..9d7d418de --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_post-page.js @@ -0,0 +1,53 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that source contents are invalidated when the target navigates. + */ + +const TAB_URL = EXAMPLE_URL + "sjs_post-page.sjs"; + +const FORM = "<form method=\"POST\"><input type=\"submit\"></form>"; +const GET_CONTENT = "<script>\"GET\";</script>" + FORM; +const POST_CONTENT = "<script>\"POST\";</script>" + FORM; + +add_task(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let editor = win.DebuggerView.editor; + let queries = win.require("./content/queries"); + let getState = win.DebuggerController.getState; + + let source = queries.getSelectedSource(getState()); + + is(queries.getSourceCount(getState()), 1, + "There should be one source displayed in the view."); + is(source.url, TAB_URL, + "The correct source is currently selected in the view."); + is(editor.getText(), GET_CONTENT, + "The currently shown source contains bacon. Mmm, delicious!"); + + // Submit the form and wait for debugger update + let onSourceUpdated = waitForSourceShown(panel, TAB_URL); + yield ContentTask.spawn(tab.linkedBrowser, null, function () { + content.document.querySelector("input[type=\"submit\"]").click(); + }); + yield onSourceUpdated; + + // Verify that the source updates to the POST page content + source = queries.getSelectedSource(getState()); + is(queries.getSourceCount(getState()), 1, + "There should be one source displayed in the view."); + is(source.url, TAB_URL, + "The correct source is currently selected in the view."); + is(editor.getText(), POST_CONTENT, + "The currently shown source contains bacon. Mmm, delicious!"); + + yield closeDebuggerAndFinish(panel); +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-01.js new file mode 100644 index 000000000..be6cb76f7 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-01.js @@ -0,0 +1,52 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that clicking the pretty print button prettifies the source. + */ + +const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html"; + +function test() { + // Wait for debugger panel to be fully set and break on debugger statement + let options = { + source: EXAMPLE_URL + "code_ugly.js", + line: 2 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + Task.spawn(function* () { + ok(!gEditor.getText().includes("\n "), + "The source shouldn't be pretty printed yet."); + + const finished = waitForSourceShown(gPanel, "code_ugly.js"); + gDebugger.document.getElementById("pretty-print").click(); + const deck = gDebugger.document.getElementById("editor-deck"); + is(deck.selectedIndex, 2, "The progress bar should be shown"); + yield finished; + + ok(gEditor.getText().includes("\n "), + "The source should be pretty printed."); + is(deck.selectedIndex, 0, "The editor should be shown"); + + const source = queries.getSelectedSource(getState()); + const { loading, text } = queries.getSourceText(getState(), source.actor); + ok(!loading, "Source text is not loading"); + ok(text.includes("\n "), + "Subsequent calls to getText return the pretty printed source."); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-02.js new file mode 100644 index 000000000..4cbdfc9a8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-02.js @@ -0,0 +1,41 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that right clicking and selecting the pretty print context menu + * item prettifies the source. + */ + +const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html"; + +function test() { + // Wait for debugger panel to be fully set and break on debugger statement + let options = { + source: EXAMPLE_URL + "code_ugly.js", + line: 2 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gContextMenu = gDebugger.document.getElementById("sourceEditorContextMenu"); + + Task.spawn(function* () { + const finished = waitForSourceShown(gPanel, "code_ugly.js"); + once(gContextMenu, "popupshown").then(() => { + const menuItem = gDebugger.document.getElementById("se-dbg-cMenu-prettyPrint"); + menuItem.click(); + }); + gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false); + yield finished; + + ok(gEditor.getText().includes("\n "), + "The source should be pretty printed."); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-03.js new file mode 100644 index 000000000..13de30ac0 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-03.js @@ -0,0 +1,40 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that we have the correct line selected after pretty printing. + */ + +const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html"; + +function test() { + // Wait for debugger panel to be fully set and break on debugger statement + let options = { + source: EXAMPLE_URL + "code_ugly.js", + line: 2 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + + Task.spawn(function* () { + yield doResume(gPanel); + + const paused = waitForPause(gDebugger.gThreadClient); + callInTab(gTab, "foo"); + yield paused; + + const finished = promise.all([ + waitForSourceShown(gPanel, "code_ugly.js"), + waitForCaretUpdated(gPanel, 7) + ]); + gDebugger.document.getElementById("pretty-print").click(); + yield finished; + + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-04.js new file mode 100644 index 000000000..a45aca91e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-04.js @@ -0,0 +1,50 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the function searching works with pretty printed sources. + */ + +const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html"; + +function test() { + // Wait for debugger panel to be fully set and break on debugger statement + let options = { + source: EXAMPLE_URL + "code_ugly.js", + line: 2 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + Task.spawn(function* () { + let popupShown = promise.defer(); + once(gDebugger, "popupshown").then(() => { + ok(isCaretPos(gPanel, 2, 10), + "The bar function's non-pretty-printed location should be shown."); + popupShown.resolve(); + }); + setText(gSearchBox, "@bar"); + yield popupShown.promise; + + const finished = waitForSourceShown(gPanel, "code_ugly.js"); + gDebugger.document.getElementById("pretty-print").click(); + yield finished; + + popupShown = promise.defer(); + once(gDebugger, "popupshown").then(() => { + ok(isCaretPos(gPanel, 6, 10), + "The bar function's pretty printed location should be shown."); + popupShown.resolve(); + }); + setText(gSearchBox, "@bar"); + yield popupShown.promise; + + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-05.js new file mode 100644 index 000000000..de1198103 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-05.js @@ -0,0 +1,66 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that prettifying HTML sources doesn't do anything. + */ + +const TAB_URL = EXAMPLE_URL + "doc_included-script.html"; +const SCRIPT_URL = EXAMPLE_URL + "code_location-changes.js"; + +function test() { + let options = { + source: SCRIPT_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + Task.spawn(function* () { + // Now, select the html page + const sourceShown = waitForSourceShown(gPanel, TAB_URL); + gSources.selectedValue = getSourceActor(gSources, TAB_URL); + yield sourceShown; + + // From this point onward, the source editor's text should never change. + gEditor.once("change", () => { + ok(false, "The source editor text shouldn't have changed."); + }); + + is(getSelectedSourceURL(gSources), TAB_URL, + "The correct source is currently selected."); + ok(gEditor.getText().includes("myFunction"), + "The source shouldn't be pretty printed yet."); + + const source = queries.getSelectedSource(getState()); + try { + yield actions.togglePrettyPrint(source); + ok(false, "An error occurred while pretty-printing"); + } + catch (err) { + is(err.message, "Can't prettify non-javascript files.", + "The promise was correctly rejected with a meaningful message."); + } + + const { text } = yield queries.getSourceText(getState(), source.actor); + is(getSelectedSourceURL(gSources), TAB_URL, + "The correct source is still selected."); + ok(gEditor.getText().includes("myFunction"), + "The displayed source hasn't changed."); + ok(text.includes("myFunction"), + "The cached source text wasn't altered in any way."); + + yield closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-06.js new file mode 100644 index 000000000..608df3140 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-06.js @@ -0,0 +1,80 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that prettifying JS sources with type errors works as expected. + */ + +const TAB_URL = EXAMPLE_URL + "doc_included-script.html"; +const JS_URL = EXAMPLE_URL + "code_location-changes.js"; + +function test() { + let options = { + source: JS_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gClient = gDebugger.gClient; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + let gPrettyPrinted = false; + + // We can't feed javascript files with syntax errors to the debugger, + // because they will never run, thus sometimes getting gc'd before the + // debugger is opened, or even before the target finishes navigating. + // Make the client lie about being able to parse perfectly fine code. + gClient.request = (function (aOriginalRequestMethod) { + return function (aPacket, aCallback) { + if (aPacket.type == "prettyPrint") { + gPrettyPrinted = true; + return promise.reject({ error: "prettyPrintError" }); + } + return aOriginalRequestMethod(aPacket, aCallback); + }; + }(gClient.request)); + + Task.spawn(function* () { + // From this point onward, the source editor's text should never change. + gEditor.once("change", () => { + ok(false, "The source editor text shouldn't have changed."); + }); + + is(getSelectedSourceURL(gSources), JS_URL, + "The correct source is currently selected."); + ok(gEditor.getText().includes("myFunction"), + "The source shouldn't be pretty printed yet."); + + const source = queries.getSelectedSource(getState()); + try { + yield actions.togglePrettyPrint(source); + ok(false, "The promise for a prettified source should be rejected!"); + } catch (error) { + ok(error.error, "Error came from a RDP request"); + ok(error.error.includes("prettyPrintError"), + "The promise was correctly rejected with a meaningful message."); + } + + const { text } = yield queries.getSourceText(getState(), source.actor); + is(getSelectedSourceURL(gSources), JS_URL, + "The correct source is still selected."); + ok(gEditor.getText().includes("myFunction"), + "The displayed source hasn't changed."); + ok(text.includes("myFunction"), + "The cached source text wasn't altered in any way."); + + is(gPrettyPrinted, true, + "The hijacked pretty print method was executed."); + + yield closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-07.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-07.js new file mode 100644 index 000000000..4776d16ba --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-07.js @@ -0,0 +1,62 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test basic pretty printing functionality. Would be an xpcshell test, except +// for bug 921252. + +var gTab, gPanel, gClient, gThreadClient, gSource; + +const TAB_URL = EXAMPLE_URL + "doc_pretty-print-2.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_ugly-2.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gClient = gPanel.panelWin.gClient; + gThreadClient = gPanel.panelWin.DebuggerController.activeThread; + + findSource(); + }); +} + +function findSource() { + gThreadClient.getSources(({ error, sources }) => { + ok(!error); + sources = sources.filter(s => s.url.includes("code_ugly-2.js")); + is(sources.length, 1); + gSource = sources[0]; + prettyPrintSource(); + }); +} + +function prettyPrintSource() { + gThreadClient.source(gSource).prettyPrint(4, testPrettyPrinted); +} + +function testPrettyPrinted({ error, source }) { + ok(!error, "Should not get an error while pretty-printing"); + ok(source.includes("\n "), + "Source should be pretty-printed"); + disablePrettyPrint(); +} + +function disablePrettyPrint() { + gThreadClient.source(gSource).disablePrettyPrint(testUgly); +} + +function testUgly({ error, source }) { + ok(!error, "Should not get an error while disabling pretty-printing"); + ok(!source.includes("\n "), + "Source should not be pretty after disabling pretty-printing"); + closeDebuggerAndFinish(gPanel); +} + +registerCleanupFunction(function () { + gTab = gPanel = gClient = gThreadClient = gSource = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-08.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-08.js new file mode 100644 index 000000000..e49910972 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-08.js @@ -0,0 +1,99 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test stepping through pretty printed sources. + +var gTab, gPanel, gClient, gThreadClient, gSource; + +const TAB_URL = EXAMPLE_URL + "doc_pretty-print-2.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_ugly-2.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gClient = gPanel.panelWin.gClient; + gThreadClient = gPanel.panelWin.DebuggerController.activeThread; + + findSource(); + }); +} + +const BP_LOCATION = { + line: 5, + // column: 0 +}; + +function findSource() { + gThreadClient.getSources(({ error, sources }) => { + ok(!error, "error should exist"); + sources = sources.filter(s => s.url.includes("code_ugly-3.js")); + is(sources.length, 1, "sources.length should be 1"); + [gSource] = sources; + BP_LOCATION.actor = gSource.actor; + + prettyPrintSource(sources[0]); + }); +} + +function prettyPrintSource(source) { + gThreadClient.source(gSource).prettyPrint(2, runCode); +} + +function runCode({ error }) { + ok(!error); + gClient.addOneTimeListener("paused", testDbgStatement); + callInTab(gTab, "main3"); +} + +function testDbgStatement(event, { why, frame }) { + is(why.type, "debuggerStatement"); + const { source, line, column } = frame.where; + is(source.actor, BP_LOCATION.actor, "source.actor should be the right actor"); + is(line, 3, "the line should be 3"); + setBreakpoint(); +} + +function setBreakpoint() { + gThreadClient.source(gSource).setBreakpoint( + { line: BP_LOCATION.line, + column: BP_LOCATION.column }, + ({ error, actualLocation }) => { + ok(!error, "error should not exist"); + ok(!actualLocation, "actualLocation should not exist"); + testStepping(); + } + ); +} + +function testStepping() { + gClient.addOneTimeListener("paused", (event, { why, frame }) => { + is(why.type, "resumeLimit"); + const { source, line } = frame.where; + is(source.actor, BP_LOCATION.actor, "source.actor should be the right actor"); + is(line, 4, "the line should be 4"); + testHitBreakpoint(); + }); + gThreadClient.stepIn(); +} + +function testHitBreakpoint() { + gClient.addOneTimeListener("paused", (event, { why, frame }) => { + is(why.type, "breakpoint"); + const { source, line } = frame.where; + is(source.actor, BP_LOCATION.actor, "source.actor should be the right actor"); + is(line, BP_LOCATION.line, "the line should the right line"); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); + gThreadClient.resume(); +} + +registerCleanupFunction(function () { + gTab = gPanel = gClient = gThreadClient = gSource = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-09.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-09.js new file mode 100644 index 000000000..16eab24ea --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-09.js @@ -0,0 +1,92 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test pretty printing source mapped sources. + +var gClient; +var gThreadClient; +var gSource; + +var gTab, gPanel, gClient, gThreadClient, gSource; + +const TAB_URL = EXAMPLE_URL + "doc_pretty-print-2.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_ugly-2.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gClient = gPanel.panelWin.gClient; + gThreadClient = gPanel.panelWin.DebuggerController.activeThread; + + findSource(); + }); +} + +const dataUrl = s => "data:text/javascript," + s; + +// These should match the instructions in code_ugly-4.js. +const A = "function a(){b()}"; +const A_URL = dataUrl(A); +const B = "function b(){debugger}"; +const B_URL = dataUrl(B); + +function findSource() { + gThreadClient.getSources(({ error, sources }) => { + ok(!error); + sources = sources.filter(s => s.url === B_URL); + is(sources.length, 1); + gSource = sources[0]; + prettyPrint(); + }); +} + +function prettyPrint() { + gThreadClient.source(gSource).prettyPrint(2, runCode); +} + +function runCode({ error }) { + ok(!error); + gClient.addOneTimeListener("paused", testDbgStatement); + callInTab(gTab, "a"); +} + +function testDbgStatement(event, { frame, why }) { + is(why.type, "debuggerStatement"); + const { source, line } = frame.where; + is(source.url, B_URL); + is(line, 2); + + disablePrettyPrint(); +} + +function disablePrettyPrint() { + gThreadClient.source(gSource).disablePrettyPrint(testUgly); +} + +function testUgly({ error, source }) { + ok(!error); + ok(!source.includes("\n ")); + getFrame(); +} + +function getFrame() { + gThreadClient.getFrames(0, 1, testFrame); +} + +function testFrame({ frames: [frame] }) { + const { source, line } = frame.where; + is(source.url, B_URL); + is(line, 1); + + resumeDebuggerThenCloseAndFinish(gPanel); +} + +registerCleanupFunction(function () { + gTab = gPanel = gClient = gThreadClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-10.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-10.js new file mode 100644 index 000000000..862d553fb --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-10.js @@ -0,0 +1,48 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that we disable the pretty print button for black boxed sources, + * and that clicking it doesn't do anything. + */ + +const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html"; + +function test() { + // Wait for debugger panel to be fully set and break on debugger statement + let options = { + source: EXAMPLE_URL + "code_ugly.js", + line: 2 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const getState = gDebugger.DebuggerController.getState; + + Task.spawn(function* () { + ok(!gEditor.getText().includes("\n "), + "The source shouldn't be pretty printed yet."); + + yield toggleBlackBoxing(gPanel); + + // Wait a tick before clicking to make sure the frontend's blackboxchange + // handlers have finished. + yield waitForTick(); + gDebugger.document.getElementById("pretty-print").click(); + // Make sure the text updates + yield waitForTick(); + + const source = queries.getSelectedSource(getState()); + const { text } = queries.getSourceText(getState(), source.actor); + ok(!text.includes("\n ")); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-11.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-11.js new file mode 100644 index 000000000..9e8bebc3f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-11.js @@ -0,0 +1,65 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that pretty printing is maintained across refreshes. + */ + +const TAB_URL = EXAMPLE_URL + "doc_pretty-print.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources; + +function test() { + // Wait for debugger panel to be fully set and break on debugger statement + let options = { + source: EXAMPLE_URL + "code_ugly.js", + line: 2 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + + testSourceIsUgly(); + const finished = waitForCaretUpdated(gPanel, 7); + clickPrettyPrintButton(); + finished.then(testSourceIsPretty) + .then(() => { + const finished = waitForCaretUpdated(gPanel, 7); + const reloaded = reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN); + return Promise.all([finished, reloaded]); + }) + .then(testSourceIsPretty) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError)); + }); + }); +} + +function testSourceIsUgly() { + ok(!gEditor.getText().includes("\n "), + "The source shouldn't be pretty printed yet."); +} + +function clickPrettyPrintButton() { + gDebugger.document.getElementById("pretty-print").click(); +} + +function testSourceIsPretty() { + ok(gEditor.getText().includes("\n "), + "The source should be pretty printed."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-12.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-12.js new file mode 100644 index 000000000..57ce54fc5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-12.js @@ -0,0 +1,51 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that we don't leave the pretty print button checked when we fail to + * pretty print a source (because it isn't a JS file, for example). + */ + +const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html"; +const SCRIPT_URL = EXAMPLE_URL + "code_blackboxing_blackboxme.js"; + +function test() { + let options = { + source: SCRIPT_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + Task.spawn(function* () { + const source = getSourceForm(gSources, TAB_URL); + let shown = ensureSourceIs(gPanel, TAB_URL, true); + actions.selectSource(source); + yield shown; + + try { + yield actions.togglePrettyPrint(source); + ok(false, "An error occurred while pretty-printing"); + } + catch (err) { + is(err.message, "Can't prettify non-javascript files.", + "The promise was correctly rejected with a meaningful message."); + } + + is(gDebugger.document.getElementById("pretty-print").checked, false, + "The button shouldn't be checked after trying to pretty print a non-js file."); + + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-13.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-13.js new file mode 100644 index 000000000..7409f88f5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-13.js @@ -0,0 +1,53 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that clicking the pretty print button prettifies the source, even + * when the source URL does not end in ".js", but the content type is + * JavaScript. + */ + +const TAB_URL = EXAMPLE_URL + "doc_pretty-print-3.html"; + +function test() { + // Wait for debugger panel to be fully set and break on debugger statement + let options = { + source: EXAMPLE_URL + "code_ugly-8", + line: 2 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + Task.spawn(function* () { + ok(!gEditor.getText().includes("\n "), + "The source shouldn't be pretty printed yet."); + + const finished = waitForSourceShown(gPanel, "code_ugly-8"); + gDebugger.document.getElementById("pretty-print").click(); + const deck = gDebugger.document.getElementById("editor-deck"); + is(deck.selectedIndex, 2, "The progress bar should be shown"); + yield finished; + + ok(gEditor.getText().includes("\n "), + "The source should be pretty printed."); + is(deck.selectedIndex, 0, "The editor should be shown"); + + const source = queries.getSelectedSource(getState()); + const { text } = queries.getSourceText(getState(), source.actor); + ok(text.includes("\n "), + "Subsequent calls to getText return the pretty printed source."); + + resumeDebuggerThenCloseAndFinish(gPanel); + }) + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-on-paused.js b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-on-paused.js new file mode 100644 index 000000000..12e3a20fc --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-on-paused.js @@ -0,0 +1,69 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that pretty printing when the debugger is paused does not switch away + * from the selected source. + */ + +const TAB_URL = EXAMPLE_URL + "doc_pretty-print-on-paused.html"; + +var gTab, gPanel, gDebugger, gThreadClient, gSources; + +const SECOND_SOURCE_VALUE = EXAMPLE_URL + "code_ugly-2.js"; + +function test() { + // Wait for debugger panel to be fully set and break on debugger statement + let options = { + source: EXAMPLE_URL + "code_script-switching-02.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gThreadClient = gDebugger.gThreadClient; + gSources = gDebugger.DebuggerView.Sources; + + Task.spawn(function* () { + try { + yield doInterrupt(gPanel); + + let source = gThreadClient.source(getSourceForm(gSources, SECOND_SOURCE_VALUE)); + yield source.setBreakpoint({ + line: 6 + }); + yield doResume(gPanel); + + const bpHit = waitForCaretAndScopes(gPanel, 6); + callInTab(gTab, "secondCall"); + yield bpHit; + + info("Switch to the second source."); + const sourceShown = waitForSourceShown(gPanel, SECOND_SOURCE_VALUE); + gSources.selectedValue = getSourceActor(gSources, SECOND_SOURCE_VALUE); + yield sourceShown; + + info("Pretty print the source."); + const prettyPrinted = waitForSourceShown(gPanel, SECOND_SOURCE_VALUE); + gDebugger.document.getElementById("pretty-print").click(); + yield prettyPrinted; + + yield resumeDebuggerThenCloseAndFinish(gPanel); + } catch (e) { + DevToolsUtils.reportException("browser_dbg_pretty-print-on-paused.js", e); + ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); + } + }); + }); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gThreadClient = null; + gSources = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_progress-listener-bug.js b/devtools/client/debugger/test/mochitest/browser_dbg_progress-listener-bug.js new file mode 100644 index 000000000..e5aac615a --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_progress-listener-bug.js @@ -0,0 +1,89 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the debugger does show up even if a progress listener reads the + * WebProgress argument's DOMWindow property in onStateChange() (bug 771655). + */ + +var gTab, gPanel, gDebugger; +var gOldListener; + +const TAB_URL = EXAMPLE_URL + "doc_inline-script.html"; + +function test() { + installListener(); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + + is(!!gDebugger.DebuggerController._startup, true, + "Controller should be initialized after starting the test."); + + testPause(); + }); +} + +function testPause() { + let onCaretUpdated = waitForCaretUpdated(gPanel, 16); + callInTab(gTab, "runDebuggerStatement"); + onCaretUpdated.then(() => { + is(gDebugger.gThreadClient.state, "paused", + "The debugger statement was reached."); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); +} + +// This is taken almost verbatim from bug 771655. +function installListener() { + if ("_testPL" in window) { + gOldListener = _testPL; + + Cc["@mozilla.org/docloaderservice;1"] + .getService(Ci.nsIWebProgress) + .removeProgressListener(_testPL); + } + + window._testPL = { + START_DOC: Ci.nsIWebProgressListener.STATE_START | + Ci.nsIWebProgressListener.STATE_IS_DOCUMENT, + onStateChange: function (wp, req, stateFlags, status) { + if ((stateFlags & this.START_DOC) === this.START_DOC) { + // This DOMWindow access triggers the unload event. + wp.DOMWindow; + } + }, + QueryInterface: function (iid) { + if (iid.equals(Ci.nsISupportsWeakReference) || + iid.equals(Ci.nsIWebProgressListener)) + return this; + throw Cr.NS_ERROR_NO_INTERFACE; + } + }; + + Cc["@mozilla.org/docloaderservice;1"] + .getService(Ci.nsIWebProgress) + .addProgressListener(_testPL, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST); +} + +registerCleanupFunction(function () { + if (gOldListener) { + window._testPL = gOldListener; + } else { + delete window._testPL; + } + + gTab = null; + gPanel = null; + gDebugger = null; + gOldListener = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_promises-allocation-stack.js b/devtools/client/debugger/test/mochitest/browser_dbg_promises-allocation-stack.js new file mode 100644 index 000000000..2d55c3a8d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_promises-allocation-stack.js @@ -0,0 +1,87 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can get a stack to a promise's allocation point. + */ + +"use strict"; + +const TAB_URL = EXAMPLE_URL + "doc_promise-get-allocation-stack.html"; +const { PromisesFront } = require("devtools/shared/fronts/promises"); +var events = require("sdk/event/core"); + +function test() { + Task.spawn(function* () { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + let options = { + source: TAB_URL, + line: 1 + }; + const [ tab,, panel ] = yield initDebugger(TAB_URL, options); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let targetTab = findTab(tabs, TAB_URL); + yield attachTab(client, targetTab); + + yield testGetAllocationStack(client, targetTab, tab); + + yield close(client); + yield closeDebuggerAndFinish(panel); + }).then(null, error => { + ok(false, "Got an error: " + error.message + "\n" + error.stack); + }); +} + +function* testGetAllocationStack(client, form, tab) { + let front = PromisesFront(client, form); + + yield front.attach(); + yield front.listPromises(); + + // Get the grip for promise p + let onNewPromise = new Promise(resolve => { + events.on(front, "new-promises", promises => { + for (let p of promises) { + if (p.preview.ownProperties.name && + p.preview.ownProperties.name.value === "p") { + resolve(p); + } + } + }); + }); + + callInTab(tab, "makePromises"); + + let grip = yield onNewPromise; + ok(grip, "Found our promise p"); + + let objectClient = new ObjectClient(client, grip); + ok(objectClient, "Got Object Client"); + + yield new Promise(resolve => { + objectClient.getPromiseAllocationStack(response => { + ok(response.allocationStack.length, "Got promise allocation stack."); + + for (let stack of response.allocationStack) { + is(stack.source.url, TAB_URL, "Got correct source URL."); + is(stack.functionDisplayName, "makePromises", + "Got correct function display name."); + is(typeof stack.line, "number", "Expect stack line to be a number."); + is(typeof stack.column, "number", + "Expect stack column to be a number."); + } + + resolve(); + }); + }); + + yield front.detach(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_promises-chrome-allocation-stack.js b/devtools/client/debugger/test/mochitest/browser_dbg_promises-chrome-allocation-stack.js new file mode 100644 index 000000000..48e9ab229 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_promises-chrome-allocation-stack.js @@ -0,0 +1,100 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can get a stack to a promise's allocation point in the chrome + * process. + */ + +"use strict"; + +const SOURCE_URL = "browser_dbg_promises-chrome-allocation-stack.js"; +const PromisesFront = require("devtools/shared/fronts/promises"); +var events = require("sdk/event/core"); + +const STACK_DATA = [ + { functionDisplayName: "test/</<" }, + { functionDisplayName: "testGetAllocationStack" }, +]; + +function test() { + Task.spawn(function* () { + requestLongerTimeout(10); + + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + DebuggerServer.allowChromeProcess = true; + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + let chrome = yield client.getProcess(); + let [, tabClient] = yield attachTab(client, chrome.form); + yield tabClient.attachThread(); + + yield testGetAllocationStack(client, chrome.form, () => { + let p = new Promise(() => {}); + p.name = "p"; + let q = p.then(); + q.name = "q"; + let r = p.then(null, () => {}); + r.name = "r"; + }); + + yield close(client); + finish(); + }).then(null, error => { + ok(false, "Got an error: " + error.message + "\n" + error.stack); + }); +} + +function* testGetAllocationStack(client, form, makePromises) { + let front = PromisesFront(client, form); + + yield front.attach(); + yield front.listPromises(); + + // Get the grip for promise p + let onNewPromise = new Promise(resolve => { + events.on(front, "new-promises", promises => { + for (let p of promises) { + if (p.preview.ownProperties.name && + p.preview.ownProperties.name.value === "p") { + resolve(p); + } + } + }); + }); + + makePromises(); + + let grip = yield onNewPromise; + ok(grip, "Found our promise p"); + + let objectClient = new ObjectClient(client, grip); + ok(objectClient, "Got Object Client"); + + yield new Promise(resolve => { + objectClient.getPromiseAllocationStack(response => { + ok(response.allocationStack.length, "Got promise allocation stack."); + + for (let i = 0; i < STACK_DATA.length; i++) { + let data = STACK_DATA[i]; + let stack = response.allocationStack[i]; + + ok(stack.source.url.startsWith("chrome:"), "Got a chrome source URL"); + ok(stack.source.url.endsWith(SOURCE_URL), "Got correct source URL."); + is(stack.functionDisplayName, data.functionDisplayName, + "Got correct function display name."); + is(typeof stack.line, "number", "Expect stack line to be a number."); + is(typeof stack.column, "number", + "Expect stack column to be a number."); + } + + resolve(); + }); + }); + + yield front.detach(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_promises-fulfillment-stack.js b/devtools/client/debugger/test/mochitest/browser_dbg_promises-fulfillment-stack.js new file mode 100644 index 000000000..a5f592eb6 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_promises-fulfillment-stack.js @@ -0,0 +1,106 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can get a stack to a promise's fulfillment point. + */ + +"use strict"; + +const TAB_URL = EXAMPLE_URL + "doc_promise-get-fulfillment-stack.html"; +const { PromisesFront } = require("devtools/shared/fronts/promises"); +var events = require("sdk/event/core"); + +const TEST_DATA = [ + { + functionDisplayName: "returnPromise/<", + line: 19, + column: 37 + }, + { + functionDisplayName: "returnPromise", + line: 19, + column: 14 + }, + { + functionDisplayName: "makePromise", + line: 14, + column: 15 + }, +]; + +function test() { + Task.spawn(function* () { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + let options = { + source: TAB_URL, + line: 1 + }; + const [ tab,, panel ] = yield initDebugger(TAB_URL, options); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let targetTab = findTab(tabs, TAB_URL); + yield attachTab(client, targetTab); + + yield testGetFulfillmentStack(client, targetTab, tab); + + yield close(client); + yield closeDebuggerAndFinish(panel); + }).then(null, error => { + ok(false, "Got an error: " + error.message + "\n" + error.stack); + }); +} + +function* testGetFulfillmentStack(client, form, tab) { + let front = PromisesFront(client, form); + + yield front.attach(); + yield front.listPromises(); + + // Get the grip for promise p + let onNewPromise = new Promise(resolve => { + events.on(front, "new-promises", promises => { + for (let p of promises) { + if (p.preview.ownProperties.name && + p.preview.ownProperties.name.value === "p") { + resolve(p); + } + } + }); + }); + + callInTab(tab, "makePromise"); + + let grip = yield onNewPromise; + ok(grip, "Found our promise p"); + + let objectClient = new ObjectClient(client, grip); + ok(objectClient, "Got Object Client"); + + yield new Promise(resolve => { + objectClient.getPromiseFulfillmentStack(response => { + ok(response.fulfillmentStack.length, "Got promise allocation stack."); + + for (let i = 0; i < TEST_DATA.length; i++) { + let stack = response.fulfillmentStack[i]; + let data = TEST_DATA[i]; + is(stack.source.url, TAB_URL, "Got correct source URL."); + is(stack.functionDisplayName, data.functionDisplayName, + "Got correct function display name."); + is(stack.line, data.line, "Got correct stack line number."); + is(stack.column, data.column, "Got correct stack column number."); + } + + resolve(); + }); + }); + + yield front.detach(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_promises-rejection-stack.js b/devtools/client/debugger/test/mochitest/browser_dbg_promises-rejection-stack.js new file mode 100644 index 000000000..9434024e3 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_promises-rejection-stack.js @@ -0,0 +1,106 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can get a stack to a promise's rejection point. + */ + +"use strict"; + +const TAB_URL = EXAMPLE_URL + "doc_promise-get-rejection-stack.html"; +const { PromisesFront } = require("devtools/shared/fronts/promises"); +var events = require("sdk/event/core"); + +const TEST_DATA = [ + { + functionDisplayName: "returnPromise/<", + line: 19, + column: 47 + }, + { + functionDisplayName: "returnPromise", + line: 19, + column: 14 + }, + { + functionDisplayName: "makePromise", + line: 14, + column: 15 + }, +]; + +function test() { + Task.spawn(function* () { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + let options = { + source: TAB_URL, + line: 1 + }; + const [ tab,, panel ] = yield initDebugger(TAB_URL, options); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let { tabs } = yield listTabs(client); + let targetTab = findTab(tabs, TAB_URL); + yield attachTab(client, targetTab); + + yield testGetRejectionStack(client, targetTab, tab); + + yield close(client); + yield closeDebuggerAndFinish(panel); + }).then(null, error => { + ok(false, "Got an error: " + error.message + "\n" + error.stack); + }); +} + +function* testGetRejectionStack(client, form, tab) { + let front = PromisesFront(client, form); + + yield front.attach(); + yield front.listPromises(); + + // Get the grip for promise p + let onNewPromise = new Promise(resolve => { + events.on(front, "new-promises", promises => { + for (let p of promises) { + if (p.preview.ownProperties.name && + p.preview.ownProperties.name.value === "p") { + resolve(p); + } + } + }); + }); + + callInTab(tab, "makePromise"); + + let grip = yield onNewPromise; + ok(grip, "Found our promise p"); + + let objectClient = new ObjectClient(client, grip); + ok(objectClient, "Got Object Client"); + + yield new Promise(resolve => { + objectClient.getPromiseRejectionStack(response => { + ok(response.rejectionStack.length, "Got promise allocation stack."); + + for (let i = 0; i < TEST_DATA.length; i++) { + let stack = response.rejectionStack[i]; + let data = TEST_DATA[i]; + is(stack.source.url, TAB_URL, "Got correct source URL."); + is(stack.functionDisplayName, data.functionDisplayName, + "Got correct function display name."); + is(stack.line, data.line, "Got correct stack line number."); + is(stack.column, data.column, "Got correct stack column number."); + } + + resolve(); + }); + }); + + yield front.detach(); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-02.js new file mode 100644 index 000000000..75e7cfc1c --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-02.js @@ -0,0 +1,50 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the preferred source is shown when a page is loaded and + * the preferred source is specified after another source might have been shown. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; +const PREFERRED_URL = EXAMPLE_URL + "code_script-switching-02.js"; + +var gTab, gPanel, gDebugger; +var gSources; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + + waitForSourceShown(gPanel, PREFERRED_URL).then(finishTest); + gSources.preferredSource = getSourceActor(gSources, PREFERRED_URL); + }); +} + +function finishTest() { + info("Currently preferred source: " + gSources.preferredValue); + info("Currently selected source: " + gSources.selectedValue); + + is(getSourceURL(gSources, gSources.preferredValue), PREFERRED_URL, + "The preferred source url wasn't set correctly."); + is(getSourceURL(gSources, gSources.selectedValue), PREFERRED_URL, + "The selected source isn't the correct one."); + + closeDebuggerAndFinish(gPanel); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gSources = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-03.js new file mode 100644 index 000000000..d286a5072 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-03.js @@ -0,0 +1,62 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the preferred source is shown when a page is loaded and + * the preferred source is specified after another source was definitely shown. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; +const FIRST_URL = EXAMPLE_URL + "code_script-switching-01.js"; +const SECOND_URL = EXAMPLE_URL + "code_script-switching-02.js"; + +var gTab, gPanel, gDebugger; +var gSources; + +function test() { + let options = { + source: FIRST_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + + testSource(undefined, FIRST_URL); + switchToSource(SECOND_URL) + .then(() => testSource(SECOND_URL)) + .then(() => switchToSource(FIRST_URL)) + .then(() => testSource(FIRST_URL)) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testSource(aPreferredUrl, aSelectedUrl = aPreferredUrl) { + info("Currently preferred source: " + gSources.preferredValue); + info("Currently selected source: " + gSources.selectedValue); + + is(getSourceURL(gSources, gSources.preferredValue), aPreferredUrl, + "The preferred source url wasn't set correctly."); + is(getSourceURL(gSources, gSources.selectedValue), aSelectedUrl, + "The selected source isn't the correct one."); +} + +function switchToSource(aUrl) { + let finished = waitForSourceShown(gPanel, aUrl); + gSources.preferredSource = getSourceActor(gSources, aUrl); + return finished; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gSources = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_reload-same-script.js b/devtools/client/debugger/test/mochitest/browser_dbg_reload-same-script.js new file mode 100644 index 000000000..754599418 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_reload-same-script.js @@ -0,0 +1,88 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the same source is shown after a page is reloaded. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; +const FIRST_URL = EXAMPLE_URL + "code_script-switching-01.js"; +const SECOND_URL = EXAMPLE_URL + "code_script-switching-02.js"; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(2); + + let options = { + source: FIRST_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = aPanel.panelWin; + const gTarget = gDebugger.gTarget; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + let gStep = 0; + + function reloadPage() { + const navigated = waitForNavigation(gPanel); + reload(gPanel); + return navigated; + } + + function switchAndReload(aUrl) { + actions.selectSource(getSourceForm(gSources, aUrl)); + return reloadPage(); + } + + function testCurrentSource(aUrl, aExpectedUrl = aUrl) { + const prefSource = getSourceURL(gSources, gSources.preferredValue); + const selSource = getSourceURL(gSources, gSources.selectedValue); + + info("Currently preferred source: '" + prefSource + "'."); + info("Currently selected source: '" + selSource + "'."); + + is(prefSource, aExpectedUrl, + "The preferred source url wasn't set correctly (" + gStep + ")."); + is(selSource, aUrl, + "The selected source isn't the correct one (" + gStep + ")."); + } + + function performTest() { + switch (gStep++) { + case 0: + testCurrentSource(FIRST_URL, null); + reloadPage().then(performTest); + break; + case 1: + testCurrentSource(FIRST_URL); + reloadPage().then(performTest); + break; + case 2: + testCurrentSource(FIRST_URL); + switchAndReload(SECOND_URL).then(performTest); + break; + case 3: + testCurrentSource(SECOND_URL); + reloadPage().then(performTest); + break; + case 4: + testCurrentSource(SECOND_URL); + reloadPage().then(performTest); + break; + case 5: + testCurrentSource(SECOND_URL); + closeDebuggerAndFinish(gPanel); + break; + } + } + + performTest(); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-01.js new file mode 100644 index 000000000..b213040c0 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-01.js @@ -0,0 +1,162 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that switching the displayed source in the UI works as advertised. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + + const gLabel1 = "code_script-switching-01.js"; + const gLabel2 = "code_script-switching-02.js"; + + function testSourcesDisplay() { + let deferred = promise.defer(); + + is(gSources.itemCount, 2, + "Found the expected number of sources. (1)"); + + is(gSources.items[0].target.querySelector(".dbg-source-item").getAttribute("tooltiptext"), + EXAMPLE_URL + "code_script-switching-01.js", + "The correct tooltip text is displayed for the first source. (1)"); + is(gSources.items[1].target.querySelector(".dbg-source-item").getAttribute("tooltiptext"), + EXAMPLE_URL + "code_script-switching-02.js", + "The correct tooltip text is displayed for the second source. (1)"); + + ok(getSourceActor(gSources, EXAMPLE_URL + gLabel1), + "First source url is incorrect. (1)"); + ok(getSourceActor(gSources, EXAMPLE_URL + gLabel2), + "Second source url is incorrect. (1)"); + + ok(gSources.getItemForAttachment(e => e.label == gLabel1), + "First source label is incorrect. (1)"); + ok(gSources.getItemForAttachment(e => e.label == gLabel2), + "Second source label is incorrect. (1)"); + + ok(gSources.selectedItem, + "There should be a selected item in the sources pane. (1)"); + is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2, + "The selected value is the sources pane is incorrect. (1)"); + + is(gEditor.getText().search(/firstCall/), -1, + "The first source is not displayed. (1)"); + is(gEditor.getText().search(/debugger/), 166, + "The second source is displayed. (1)"); + + ok(isCaretPos(gPanel, 6), + "Editor caret location is correct. (1)"); + + // The editor's debug location takes a tick to update. + is(gEditor.getDebugLocation(), 5, + "Editor debugger location is correct. (1)"); + ok(gEditor.hasLineClass(5, "debug-line"), + "The debugged line is highlighted appropriately (1)."); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve); + gSources.selectedIndex = 0; + + return deferred.promise; + } + + function testSwitchPaused1() { + let deferred = promise.defer(); + + ok(gSources.selectedItem, + "There should be a selected item in the sources pane. (2)"); + is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1, + "The selected value is the sources pane is incorrect. (2)"); + + is(gEditor.getText().search(/firstCall/), 118, + "The first source is displayed. (2)"); + is(gEditor.getText().search(/debugger/), -1, + "The second source is not displayed. (2)"); + + // The editor's debug location takes a tick to update. + ok(isCaretPos(gPanel, 1), + "Editor caret location is correct. (2)"); + is(gEditor.getDebugLocation(), null, + "Editor debugger location is correct. (2)"); + ok(!gEditor.hasLineClass(5, "debug-line"), + "The debugged line highlight was removed. (2)"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve); + gSources.selectedIndex = 1; + return deferred.promise; + } + + function testSwitchPaused2() { + let deferred = promise.defer(); + + ok(gSources.selectedItem, + "There should be a selected item in the sources pane. (3)"); + is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2, + "The selected value is the sources pane is incorrect. (3)"); + + is(gEditor.getText().search(/firstCall/), -1, + "The first source is not displayed. (3)"); + is(gEditor.getText().search(/debugger/), 166, + "The second source is displayed. (3)"); + + ok(isCaretPos(gPanel, 6), + "Editor caret location is correct. (3)"); + is(gEditor.getDebugLocation(), 5, + "Editor debugger location is correct. (3)"); + ok(gEditor.hasLineClass(5, "debug-line"), + "The debugged line is highlighted appropriately (3)."); + + // Step out twice. + waitForThreadEvents(gPanel, "paused").then(() => { + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve); + gDebugger.gThreadClient.stepOut(); + }); + gDebugger.gThreadClient.stepOut(); + + return deferred.promise; + } + + function testSwitchRunning() { + ok(gSources.selectedItem, + "There should be a selected item in the sources pane. (4)"); + is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1, + "The selected value is the sources pane is incorrect. (4)"); + + is(gEditor.getText().search(/firstCall/), 118, + "The first source is displayed. (4)"); + is(gEditor.getText().search(/debugger/), -1, + "The second source is not displayed. (4)"); + + ok(isCaretPos(gPanel, 6), + "Editor caret location is correct. (4)"); + is(gEditor.getDebugLocation(), 5, + "Editor debugger location is correct. (4)"); + ok(gEditor.hasLineClass(5, "debug-line"), + "The debugged line is highlighted appropriately (3). (4)"); + } + + Task.spawn(function* () { + const shown = waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1); + callInTab(gTab, "firstCall"); + yield shown; + + yield testSourcesDisplay(); + yield testSwitchPaused1(); + yield testSwitchPaused2(); + yield testSwitchRunning(); + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-02.js new file mode 100644 index 000000000..0d524db2c --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-02.js @@ -0,0 +1,163 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that switching the displayed source in the UI works as advertised. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-02.html"; + +var gLabel1 = "code_script-switching-01.js"; +var gLabel2 = "code_script-switching-02.js"; +var gParams = "?foo=bar,baz|lol"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + + function testSourcesDisplay() { + let deferred = promise.defer(); + + is(gSources.itemCount, 2, + "Found the expected number of sources. (1)"); + + ok(getSourceActor(gSources, EXAMPLE_URL + gLabel1), + "First source url is incorrect. (1)"); + ok(getSourceActor(gSources, EXAMPLE_URL + gLabel2 + gParams), + "Second source url is incorrect. (1)"); + + ok(gSources.getItemForAttachment(e => e.label == gLabel1), + "First source label is incorrect. (1)"); + ok(gSources.getItemForAttachment(e => e.label == gLabel2), + "Second source label is incorrect. (1)"); + + ok(gSources.selectedItem, + "There should be a selected item in the sources pane. (1)"); + is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2 + gParams, + "The selected value is the sources pane is incorrect. (1)"); + + is(gEditor.getText().search(/firstCall/), -1, + "The first source is not displayed. (1)"); + is(gEditor.getText().search(/debugger/), 166, + "The second source is displayed. (1)"); + + ok(isCaretPos(gPanel, 6), + "Editor caret location is correct. (1)"); + is(gEditor.getDebugLocation(), 5, + "Editor debugger location is correct. (1)"); + ok(gEditor.hasLineClass(5, "debug-line"), + "The debugged line is highlighted appropriately. (1)"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve); + gSources.selectedItem = e => e.attachment.label == gLabel1; + + return deferred.promise; + } + + function testSwitchPaused1() { + let deferred = promise.defer(); + + ok(gSources.selectedItem, + "There should be a selected item in the sources pane. (2)"); + is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1, + "The selected value is the sources pane is incorrect. (2)"); + + is(gEditor.getText().search(/firstCall/), 118, + "The first source is displayed. (2)"); + is(gEditor.getText().search(/debugger/), -1, + "The second source is not displayed. (2)"); + + // The editor's debug location takes a tick to update. + ok(isCaretPos(gPanel, 1), + "Editor caret location is correct. (2)"); + + is(gEditor.getDebugLocation(), null, + "Editor debugger location is correct. (2)"); + ok(!gEditor.hasLineClass(5, "debug-line"), + "The debugged line highlight was removed. (2)"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve); + gSources.selectedItem = e => e.attachment.label == gLabel2; + + return deferred.promise; + } + + function testSwitchPaused2() { + let deferred = promise.defer(); + + ok(gSources.selectedItem, + "There should be a selected item in the sources pane. (3)"); + is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel2 + gParams, + "The selected value is the sources pane is incorrect. (3)"); + + is(gEditor.getText().search(/firstCall/), -1, + "The first source is not displayed. (3)"); + is(gEditor.getText().search(/debugger/), 166, + "The second source is displayed. (3)"); + + // The editor's debug location takes a tick to update. + ok(isCaretPos(gPanel, 6), + "Editor caret location is correct. (3)"); + is(gEditor.getDebugLocation(), 5, + "Editor debugger location is correct. (3)"); + ok(gEditor.hasLineClass(5, "debug-line"), + "The debugged line is highlighted appropriately. (3)"); + + // Step out three times. + waitForThreadEvents(gPanel, "paused").then(() => { + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(deferred.resolve); + gDebugger.gThreadClient.stepOut(); + }); + gDebugger.gThreadClient.stepOut(); + + return deferred.promise; + } + + function testSwitchRunning() { + let deferred = promise.defer(); + + ok(gSources.selectedItem, + "There should be a selected item in the sources pane. (4)"); + is(getSelectedSourceURL(gSources), EXAMPLE_URL + gLabel1, + "The selected value is the sources pane is incorrect. (4)"); + + is(gEditor.getText().search(/firstCall/), 118, + "The first source is displayed. (4)"); + is(gEditor.getText().search(/debugger/), -1, + "The second source is not displayed. (4)"); + + // The editor's debug location takes a tick to update. + ok(isCaretPos(gPanel, 6), + "Editor caret location is correct. (4)"); + is(gEditor.getDebugLocation(), 5, + "Editor debugger location is correct. (4)"); + ok(gEditor.hasLineClass(5, "debug-line"), + "The debugged line is highlighted appropriately. (4)"); + + deferred.resolve(); + + return deferred.promise; + } + + Task.spawn(function* () { + yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1); + yield testSourcesDisplay(); + yield testSwitchPaused1(); + yield testSwitchPaused2(); + yield testSwitchRunning(); + resumeDebuggerThenCloseAndFinish(gPanel); + }); + + callInTab(gTab, "firstCall"); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-03.js new file mode 100644 index 000000000..ab691b03c --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_scripts-switching-03.js @@ -0,0 +1,63 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the DebuggerView error loading source text is correct. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gView = gDebugger.DebuggerView; + const gEditor = gDebugger.DebuggerView.editor; + const gL10N = gDebugger.L10N; + const require = gDebugger.require; + const actions = bindActionCreators(gPanel); + const constants = require("./content/constants"); + const controller = gDebugger.DebuggerController; + + function showBogusSource() { + const source = { actor: "fake.actor", url: "http://fake.url/" }; + actions.newSource(source); + + controller.dispatch({ + type: constants.LOAD_SOURCE_TEXT, + source: source, + status: "start" + }); + + controller.dispatch({ + type: constants.SELECT_SOURCE, + source: source + }); + + controller.dispatch({ + type: constants.LOAD_SOURCE_TEXT, + source: source, + status: "error", + error: "bogus actor" + }); + } + + function testDebuggerLoadingError() { + ok(gEditor.getText().includes(gL10N.getFormatStr("errorLoadingText2", "")), + "The valid error loading message is displayed."); + } + + Task.spawn(function* () { + showBogusSource(); + testDebuggerLoadingError(); + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-autofill-identifier.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-autofill-identifier.js new file mode 100644 index 000000000..b86666ef5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-autofill-identifier.js @@ -0,0 +1,138 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that Debugger Search uses the identifier under cursor if nothing is + * selected or manually passed and searching using certain operators. + */ +"use strict"; + +function test() { + const TAB_URL = EXAMPLE_URL + "doc_function-search.html"; + + let options = { + source: EXAMPLE_URL + "code_function-search-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + let Debugger = aPanel.panelWin; + let Editor = Debugger.DebuggerView.editor; + let Filtering = Debugger.DebuggerView.Filtering; + + function doSearch(aOperator) { + Editor.dropSelection(); + Filtering._doSearch(aOperator); + } + + info("Testing with cursor at the beginning of the file..."); + + doSearch(); + is(Filtering._searchbox.value, "", + "The searchbox value should not be auto-filled when searching for files."); + is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd, + "The searchbox contents should not be selected"); + is(Editor.getSelection(), "", + "The selection in the editor should be empty."); + + doSearch("!"); + is(Filtering._searchbox.value, "!", + "The searchbox value should not be auto-filled when searching across all files."); + is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd, + "The searchbox contents should not be selected"); + is(Editor.getSelection(), "", + "The selection in the editor should be empty."); + + doSearch("@"); + is(Filtering._searchbox.value, "@", + "The searchbox value should not be auto-filled when searching for functions."); + is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd, + "The searchbox contents should not be selected"); + is(Editor.getSelection(), "", + "The selection in the editor should be empty."); + + doSearch("#"); + is(Filtering._searchbox.value, "#", + "The searchbox value should not be auto-filled when searching inside a file."); + is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd, + "The searchbox contents should not be selected"); + is(Editor.getSelection(), "", + "The selection in the editor should be empty."); + + doSearch(":"); + is(Filtering._searchbox.value, ":", + "The searchbox value should not be auto-filled when searching for a line."); + is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd, + "The searchbox contents should not be selected"); + is(Editor.getSelection(), "", + "The selection in the editor should be empty."); + + doSearch("*"); + is(Filtering._searchbox.value, "*", + "The searchbox value should not be auto-filled when searching for variables."); + is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd, + "The searchbox contents should not be selected"); + is(Editor.getSelection(), "", + "The selection in the editor should be empty."); + + Editor.setCursor({ line: 7, ch: 0}); + info("Testing with cursor at line 8 and char 1..."); + + doSearch(); + is(Filtering._searchbox.value, "", + "The searchbox value should not be auto-filled when searching for files."); + is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd, + "The searchbox contents should not be selected"); + is(Editor.getSelection(), "", + "The selection in the editor should be empty."); + + doSearch("!"); + is(Filtering._searchbox.value, "!test", + "The searchbox value was incorrect when searching across all files."); + is(Filtering._searchbox.selectionStart, 1, + "The searchbox operator should not be selected"); + is(Filtering._searchbox.selectionEnd, 5, + "The searchbox contents should be selected"); + is(Editor.getSelection(), "", + "The selection in the editor should be empty."); + + doSearch("@"); + is(Filtering._searchbox.value, "@test", + "The searchbox value was incorrect when searching for functions."); + is(Filtering._searchbox.selectionStart, 1, + "The searchbox operator should not be selected"); + is(Filtering._searchbox.selectionEnd, 5, + "The searchbox contents should be selected"); + is(Editor.getSelection(), "", + "The selection in the editor should be empty."); + + doSearch("#"); + is(Filtering._searchbox.value, "#test", + "The searchbox value should be auto-filled when searching inside a file."); + is(Filtering._searchbox.selectionStart, 1, + "The searchbox operator should not be selected"); + is(Filtering._searchbox.selectionEnd, 5, + "The searchbox contents should be selected"); + is(Editor.getSelection(), "test", + "The selection in the editor should be 'test'."); + + doSearch(":"); + is(Filtering._searchbox.value, ":", + "The searchbox value should not be auto-filled when searching for a line."); + is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd, + "The searchbox contents should not be selected"); + is(Editor.getSelection(), "", + "The selection in the editor should be empty."); + + doSearch("*"); + is(Filtering._searchbox.value, "*", + "The searchbox value should not be auto-filled when searching for variables."); + is(Filtering._searchbox.selectionStart, Filtering._searchbox.selectionEnd, + "The searchbox contents should not be selected"); + is(Editor.getSelection(), "", + "The selection in the editor should be empty."); + + closeDebuggerAndFinish(aPanel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-01.js new file mode 100644 index 000000000..e2262d4e8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-01.js @@ -0,0 +1,330 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests basic search functionality (find token and jump to line). + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources, gFiltering, gSearchBox; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gFiltering = gDebugger.DebuggerView.Filtering; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + performTest(); + }); +} + +function performTest() { + // Make sure that the search box becomes focused when pressing ctrl+f - Bug 1211038 + gEditor.focus(); + synthesizeKeyFromKeyTag(gDebugger.document.getElementById("tokenSearchKey")); + let focusedEl = Services.focus.focusedElement; + focusedEl = focusedEl.ownerDocument.getBindingParent(focusedEl) || focusedEl; + is(focusedEl, gDebugger.document.getElementById("searchbox"), "Searchbox is focused"); + + setText(gSearchBox, "#html"); + + EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true }, gDebugger); + is(gFiltering.searchData.toSource(), '["#", ["", "html"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 35, 7), + "The editor didn't jump to the correct line."); + + EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true }, gDebugger); + is(gFiltering.searchData.toSource(), '["#", ["", "html"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 5, 6), + "The editor didn't jump to the correct line."); + + EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true }, gDebugger); + is(gFiltering.searchData.toSource(), '["#", ["", "html"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 3, 15), + "The editor didn't jump to the correct line."); + + setText(gSearchBox, ":12"); + is(gFiltering.searchData.toSource(), '[":", ["", 12]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 12), + "The editor didn't jump to the correct line."); + + EventUtils.synthesizeKey("g", { metaKey: true }, gDebugger); + is(gFiltering.searchData.toSource(), '[":", ["", 13]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 13), + "The editor didn't jump to the correct line after Meta+G."); + + EventUtils.synthesizeKey("n", { ctrlKey: true }, gDebugger); + is(gFiltering.searchData.toSource(), '[":", ["", 14]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14), + "The editor didn't jump to the correct line after Ctrl+N."); + + EventUtils.synthesizeKey("G", { metaKey: true, shiftKey: true }, gDebugger); + is(gFiltering.searchData.toSource(), '[":", ["", 13]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 13), + "The editor didn't jump to the correct line after Meta+Shift+G."); + + EventUtils.synthesizeKey("p", { ctrlKey: true }, gDebugger); + is(gFiltering.searchData.toSource(), '[":", ["", 12]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 12), + "The editor didn't jump to the correct line after Ctrl+P."); + + for (let i = 0; i < 100; i++) { + EventUtils.sendKey("DOWN", gDebugger); + } + is(gFiltering.searchData.toSource(), '[":", ["", 36]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 36), + "The editor didn't jump to the correct line after multiple DOWN keys."); + + for (let i = 0; i < 100; i++) { + EventUtils.sendKey("UP", gDebugger); + } + is(gFiltering.searchData.toSource(), '[":", ["", 1]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 1), + "The editor didn't jump to the correct line after multiple UP keys."); + + + let token = "debugger"; + setText(gSearchBox, "#" + token); + is(gFiltering.searchData.toSource(), '["#", ["", "debugger"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 8, 12 + token.length), + "The editor didn't jump to the correct token (1)."); + + EventUtils.sendKey("DOWN", gDebugger); + is(gFiltering.searchData.toSource(), '["#", ["", "debugger"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14, 9 + token.length), + "The editor didn't jump to the correct token (2)."); + + EventUtils.sendKey("DOWN", gDebugger); + is(gFiltering.searchData.toSource(), '["#", ["", "debugger"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 18, 15 + token.length), + "The editor didn't jump to the correct token (3)."); + + EventUtils.sendKey("RETURN", gDebugger); + is(gFiltering.searchData.toSource(), '["#", ["", "debugger"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 26, 11 + token.length), + "The editor didn't jump to the correct token (4)."); + + EventUtils.sendKey("RETURN", gDebugger); + is(gFiltering.searchData.toSource(), '["#", ["", "debugger"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 8, 12 + token.length), + "The editor didn't jump to the correct token (5)."); + + EventUtils.sendKey("UP", gDebugger); + is(gFiltering.searchData.toSource(), '["#", ["", "debugger"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 26, 11 + token.length), + "The editor didn't jump to the correct token (6)."); + + setText(gSearchBox, ":bogus#" + token + ";"); + is(gFiltering.searchData.toSource(), '["#", [":bogus", "debugger;"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14, 9 + token.length + 1), + "The editor didn't jump to the correct token (7)."); + + setText(gSearchBox, ":13#" + token + ";"); + is(gFiltering.searchData.toSource(), '["#", [":13", "debugger;"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14, 9 + token.length + 1), + "The editor didn't jump to the correct token (8)."); + + setText(gSearchBox, ":#" + token + ";"); + is(gFiltering.searchData.toSource(), '["#", [":", "debugger;"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14, 9 + token.length + 1), + "The editor didn't jump to the correct token (9)."); + + setText(gSearchBox, "::#" + token + ";"); + is(gFiltering.searchData.toSource(), '["#", ["::", "debugger;"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14, 9 + token.length + 1), + "The editor didn't jump to the correct token (10)."); + + setText(gSearchBox, ":::#" + token + ";"); + is(gFiltering.searchData.toSource(), '["#", [":::", "debugger;"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14, 9 + token.length + 1), + "The editor didn't jump to the correct token (11)."); + + + setText(gSearchBox, "#" + token + ";" + ":bogus"); + is(gFiltering.searchData.toSource(), '["#", ["", "debugger;:bogus"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14, 9 + token.length + 1), + "The editor didn't jump to the correct token (12)."); + + setText(gSearchBox, "#" + token + ";" + ":13"); + is(gFiltering.searchData.toSource(), '["#", ["", "debugger;:13"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14, 9 + token.length + 1), + "The editor didn't jump to the correct token (13)."); + + setText(gSearchBox, "#" + token + ";" + ":"); + is(gFiltering.searchData.toSource(), '["#", ["", "debugger;:"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14, 9 + token.length + 1), + "The editor didn't jump to the correct token (14)."); + + setText(gSearchBox, "#" + token + ";" + "::"); + is(gFiltering.searchData.toSource(), '["#", ["", "debugger;::"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14, 9 + token.length + 1), + "The editor didn't jump to the correct token (15)."); + + setText(gSearchBox, "#" + token + ";" + ":::"); + is(gFiltering.searchData.toSource(), '["#", ["", "debugger;:::"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14, 9 + token.length + 1), + "The editor didn't jump to the correct token (16)."); + + + setText(gSearchBox, ":i am not a number"); + is(gFiltering.searchData.toSource(), '[":", ["", 0]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14, 9 + token.length + 1), + "The editor didn't remain at the correct token (17)."); + + setText(gSearchBox, "#__i do not exist__"); + is(gFiltering.searchData.toSource(), '["#", ["", "__i do not exist__"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14, 9 + token.length + 1), + "The editor didn't remain at the correct token (18)."); + + + setText(gSearchBox, "#" + token); + is(gFiltering.searchData.toSource(), '["#", ["", "debugger"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 8, 12 + token.length), + "The editor didn't jump to the correct token (19)."); + + + clearText(gSearchBox); + is(gFiltering.searchData.toSource(), '["", [""]]', + "The searchbox data wasn't parsed correctly."); + + EventUtils.sendKey("RETURN", gDebugger); + is(gFiltering.searchData.toSource(), '["", [""]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 8, 12 + token.length), + "The editor shouldn't jump to another token (20)."); + + EventUtils.sendKey("RETURN", gDebugger); + is(gFiltering.searchData.toSource(), '["", [""]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 8, 12 + token.length), + "The editor shouldn't jump to another token (21)."); + + + setText(gSearchBox, ":1:2:3:a:b:c:::12"); + is(gFiltering.searchData.toSource(), '[":", [":1:2:3:a:b:c::", 12]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 12), + "The editor didn't jump to the correct line (22)."); + + setText(gSearchBox, "#don't#find#me#instead#find#" + token); + is(gFiltering.searchData.toSource(), '["#", ["#don\'t#find#me#instead#find", "debugger"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 8, 12 + token.length), + "The editor didn't jump to the correct token (23)."); + + EventUtils.sendKey("DOWN", gDebugger); + is(gFiltering.searchData.toSource(), '["#", ["#don\'t#find#me#instead#find", "debugger"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 14, 9 + token.length), + "The editor didn't jump to the correct token (24)."); + + EventUtils.sendKey("DOWN", gDebugger); + is(gFiltering.searchData.toSource(), '["#", ["#don\'t#find#me#instead#find", "debugger"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 18, 15 + token.length), + "The editor didn't jump to the correct token (25)."); + + EventUtils.sendKey("RETURN", gDebugger); + is(gFiltering.searchData.toSource(), '["#", ["#don\'t#find#me#instead#find", "debugger"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 26, 11 + token.length), + "The editor didn't jump to the correct token (26)."); + + EventUtils.sendKey("RETURN", gDebugger); + is(gFiltering.searchData.toSource(), '["#", ["#don\'t#find#me#instead#find", "debugger"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 8, 12 + token.length), + "The editor didn't jump to the correct token (27)."); + + EventUtils.sendKey("UP", gDebugger); + is(gFiltering.searchData.toSource(), '["#", ["#don\'t#find#me#instead#find", "debugger"]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 26, 11 + token.length), + "The editor didn't jump to the correct token (28)."); + + + clearText(gSearchBox); + is(gFiltering.searchData.toSource(), '["", [""]]', + "The searchbox data wasn't parsed correctly."); + ok(isCaretPos(gPanel, 26, 11 + token.length), + "The editor didn't remain at the correct token (29)."); + is(gSources.visibleItems.length, 1, + "Not all the sources are shown after the search (30)."); + + + gEditor.focus(); + gEditor.setSelection.apply(gEditor, gEditor.getPosition(1, 5)); + ok(isCaretPos(gPanel, 1, 6), + "The editor caret position didn't update after selecting some text."); + + EventUtils.synthesizeKey("F", { accelKey: true }); + is(gFiltering.searchData.toSource(), '["#", ["", "!-- "]]', + "The searchbox data wasn't parsed correctly."); + is(gSearchBox.value, "#!-- ", + "The search field has the right initial value (1)."); + + gEditor.focus(); + gEditor.setSelection.apply(gEditor, gEditor.getPosition(415, 418)); + ok(isCaretPos(gPanel, 21, 30), + "The editor caret position didn't update after selecting some number."); + + EventUtils.synthesizeKey("L", { accelKey: true }); + is(gFiltering.searchData.toSource(), '[":", ["", 100]]', + "The searchbox data wasn't parsed correctly."); + is(gSearchBox.value, ":100", + "The search field has the right initial value (2)."); + + + closeDebuggerAndFinish(gPanel); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; + gFiltering = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-02.js new file mode 100644 index 000000000..ef09e16da --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-02.js @@ -0,0 +1,129 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests basic file search functionality. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +var gTab, gPanel, gDebugger; +var gSources, gSearchBox; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1, + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + // Calling `firstCall` is going to break into the other script + waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6) + .then(performSimpleSearch) + .then(() => verifySourceAndCaret("-01.js", 1, 1, [1, 1])) + .then(combineWithLineSearch) + .then(() => verifySourceAndCaret("-01.js", 2, 1, [53, 53])) + .then(combineWithTokenSearch) + .then(() => verifySourceAndCaret("-01.js", 2, 48, [96, 100])) + .then(combineWithTokenColonSearch) + .then(() => verifySourceAndCaret("-01.js", 2, 11, [56, 63])) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + callInTab(gTab, "firstCall"); + }); +} + +function performSimpleSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-02.js"), + ensureCaretAt(gPanel, 6), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForSourceShown(gPanel, "-01.js") + ]); + + setText(gSearchBox, "1"); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1) + ])); +} + +function combineWithLineSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForCaretUpdated(gPanel, 2) + ]); + + typeText(gSearchBox, ":2"); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 2) + ])); +} + +function combineWithTokenSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 2), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForCaretUpdated(gPanel, 2, 48) + ]); + + backspaceText(gSearchBox, 2); + typeText(gSearchBox, "#zero"); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 2, 48) + ])); +} + +function combineWithTokenColonSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 2, 48), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForCaretUpdated(gPanel, 2, 11) + ]); + + backspaceText(gSearchBox, 4); + typeText(gSearchBox, "http://"); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 2, 11) + ])); +} + +function verifySourceAndCaret(aUrl, aLine, aColumn, aSelection) { + ok(gSources.selectedItem.attachment.label.includes(aUrl), + "The selected item's label appears to be correct."); + ok(gSources.selectedItem.attachment.source.url.includes(aUrl), + "The selected item's value appears to be correct."); + ok(isCaretPos(gPanel, aLine, aColumn), + "The current caret position appears to be correct."); + ok(isEditorSel(gPanel, aSelection), + "The current editor selection appears to be correct."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gSources = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-03.js new file mode 100644 index 000000000..0020776d6 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-03.js @@ -0,0 +1,123 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that searches which cause a popup to be shown properly handle the + * ESCAPE key. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +var gTab, gPanel, gDebugger; +var gSources, gSearchBox; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1, + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + // Calling `firstCall` is going to break into the other script + waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6) + .then(performFileSearch) + .then(escapeAndHide) + .then(escapeAndClear) + .then(() => verifySourceAndCaret("-01.js", 1, 1)) + .then(performFunctionSearch) + .then(escapeAndHide) + .then(escapeAndClear) + .then(() => verifySourceAndCaret("-01.js", 4, 10)) + .then(performGlobalSearch) + .then(escapeAndClear) + .then(() => verifySourceAndCaret("-01.js", 4, 10)) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + callInTab(gTab, "firstCall"); + }); +} + +function performFileSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-02.js"), + ensureCaretAt(gPanel, 6), + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForSourceShown(gPanel, "-01.js") + ]); + + setText(gSearchBox, "."); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1) + ])); +} + +function performFunctionSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FUNCTION_SEARCH_MATCH_FOUND) + ]); + + setText(gSearchBox, "@"); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 4, 10) + ])); +} + +function performGlobalSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 4, 10), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND) + ]); + + setText(gSearchBox, "!first"); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 4, 10) + ])); +} + +function escapeAndHide() { + let finished = once(gDebugger, "popuphidden", true); + EventUtils.sendKey("ESCAPE", gDebugger); + return finished; +} + +function escapeAndClear() { + EventUtils.sendKey("ESCAPE", gDebugger); + is(gSearchBox.getAttribute("value"), "", + "The searchbox has properly emptied after pressing escape."); +} + +function verifySourceAndCaret(aUrl, aLine, aColumn) { + ok(gSources.selectedItem.attachment.label.includes(aUrl), + "The selected item's label appears to be correct."); + ok(gSources.selectedItem.attachment.source.url.includes(aUrl), + "The selected item's value appears to be correct."); + ok(isCaretPos(gPanel, aLine, aColumn), + "The current caret position appears to be correct."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gSources = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-04.js new file mode 100644 index 000000000..4d708797d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-04.js @@ -0,0 +1,132 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the selection is dropped for line and token searches, after + * pressing backspace enough times. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources, gSearchBox; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1, + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + testLineSearch(); + testTokenSearch(); + closeDebuggerAndFinish(gPanel); + }); +} + +function testLineSearch() { + setText(gSearchBox, ":42"); + ok(isCaretPos(gPanel, 7), + "The editor caret position appears to be correct (1.1)."); + ok(isEditorSel(gPanel, [151, 151]), + "The editor selection appears to be correct (1.1)."); + is(gEditor.getSelection(), "", + "The editor selected text appears to be correct (1.1)."); + + backspaceText(gSearchBox, 1); + ok(isCaretPos(gPanel, 4), + "The editor caret position appears to be correct (1.2)."); + ok(isEditorSel(gPanel, [110, 110]), + "The editor selection appears to be correct (1.2)."); + is(gEditor.getSelection(), "", + "The editor selected text appears to be correct (1.2)."); + + backspaceText(gSearchBox, 1); + ok(isCaretPos(gPanel, 4), + "The editor caret position appears to be correct (1.3)."); + ok(isEditorSel(gPanel, [110, 110]), + "The editor selection appears to be correct (1.3)."); + is(gEditor.getSelection(), "", + "The editor selected text appears to be correct (1.3)."); + + setText(gSearchBox, ":4"); + ok(isCaretPos(gPanel, 4), + "The editor caret position appears to be correct (1.4)."); + ok(isEditorSel(gPanel, [110, 110]), + "The editor selection appears to be correct (1.4)."); + is(gEditor.getSelection(), "", + "The editor selected text appears to be correct (1.4)."); + + gSearchBox.select(); + backspaceText(gSearchBox, 1); + ok(isCaretPos(gPanel, 4), + "The editor caret position appears to be correct (1.5)."); + ok(isEditorSel(gPanel, [110, 110]), + "The editor selection appears to be correct (1.5)."); + is(gEditor.getSelection(), "", + "The editor selected text appears to be correct (1.5)."); + is(gSearchBox.value, "", + "The searchbox should have been cleared."); +} + +function testTokenSearch() { + setText(gSearchBox, "#();"); + ok(isCaretPos(gPanel, 5, 16), + "The editor caret position appears to be correct (2.1)."); + ok(isEditorSel(gPanel, [145, 148]), + "The editor selection appears to be correct (2.1)."); + is(gEditor.getSelection(), "();", + "The editor selected text appears to be correct (2.1)."); + + backspaceText(gSearchBox, 1); + ok(isCaretPos(gPanel, 4, 21), + "The editor caret position appears to be correct (2.2)."); + ok(isEditorSel(gPanel, [128, 130]), + "The editor selection appears to be correct (2.2)."); + is(gEditor.getSelection(), "()", + "The editor selected text appears to be correct (2.2)."); + + backspaceText(gSearchBox, 2); + ok(isCaretPos(gPanel, 4, 20), + "The editor caret position appears to be correct (2.3)."); + ok(isEditorSel(gPanel, [129, 129]), + "The editor selection appears to be correct (2.3)."); + is(gEditor.getSelection(), "", + "The editor selected text appears to be correct (2.3)."); + + setText(gSearchBox, "#;"); + ok(isCaretPos(gPanel, 5, 16), + "The editor caret position appears to be correct (2.4)."); + ok(isEditorSel(gPanel, [147, 148]), + "The editor selection appears to be correct (2.4)."); + is(gEditor.getSelection(), ";", + "The editor selected text appears to be correct (2.4)."); + + gSearchBox.select(); + backspaceText(gSearchBox, 1); + ok(isCaretPos(gPanel, 5, 16), + "The editor caret position appears to be correct (2.5)."); + ok(isEditorSel(gPanel, [148, 148]), + "The editor selection appears to be correct (2.5)."); + is(gEditor.getSelection(), "", + "The editor selected text appears to be correct (2.5)."); + is(gSearchBox.value, "", + "The searchbox should have been cleared."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-01.js new file mode 100644 index 000000000..c301dbbbc --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-01.js @@ -0,0 +1,278 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests basic functionality of global search (lowercase + upper case, expected + * UI behavior, number of results found etc.) + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources, gSearchView, gSearchBox; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gSearchView = gDebugger.DebuggerView.GlobalSearch; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1) + .then(firstSearch) + .then(secondSearch) + .then(clearSearch) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + callInTab(gTab, "firstCall"); + }); +} + +function firstSearch() { + let deferred = promise.defer(); + + is(gSearchView.itemCount, 0, + "The global search pane shouldn't have any entries yet."); + is(gSearchView.widget._parent.hidden, true, + "The global search pane shouldn't be visible yet."); + is(gSearchView._splitter.hidden, true, + "The global search pane splitter shouldn't be visible yet."); + + gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => { + // Some operations are synchronously dispatched on the main thread, + // to avoid blocking UI, thus giving the impression of faster searching. + executeSoon(() => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(isCaretPos(gPanel, 6), + "The editor shouldn't have jumped to a matching line yet."); + ok(getSelectedSourceURL(gSources).includes("-02.js"), + "The current source shouldn't have changed after a global search."); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search."); + + let sourceResults = gDebugger.document.querySelectorAll(".dbg-source-results"); + is(sourceResults.length, 2, + "There should be matches found in two sources."); + + let item0 = gDebugger.SourceResults.getItemForElement(sourceResults[0]); + let item1 = gDebugger.SourceResults.getItemForElement(sourceResults[1]); + is(item0.instance.expanded, true, + "The first source results should automatically be expanded."); + is(item1.instance.expanded, true, + "The second source results should automatically be expanded."); + + let searchResult0 = sourceResults[0].querySelectorAll(".dbg-search-result"); + let searchResult1 = sourceResults[1].querySelectorAll(".dbg-search-result"); + is(searchResult0.length, 1, + "There should be one line result for the first url."); + is(searchResult1.length, 2, + "There should be two line results for the second url."); + + let firstLine0 = searchResult0[0]; + is(firstLine0.querySelector(".dbg-results-line-number").getAttribute("value"), "1", + "The first result for the first source doesn't have the correct line attached."); + + is(firstLine0.querySelectorAll(".dbg-results-line-contents").length, 1, + "The first result for the first source doesn't have the correct number of nodes for a line."); + is(firstLine0.querySelectorAll(".dbg-results-line-contents-string").length, 3, + "The first result for the first source doesn't have the correct number of strings in a line."); + + is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=true]").length, 1, + "The first result for the first source doesn't have the correct number of matches in a line."); + is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=true]")[0].getAttribute("value"), "de", + "The first result for the first source doesn't have the correct match in a line."); + + is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=false]").length, 2, + "The first result for the first source doesn't have the correct number of non-matches in a line."); + is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=false]")[0].getAttribute("value"), "/* Any copyright is ", + "The first result for the first source doesn't have the correct non-matches in a line."); + is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=false]")[1].getAttribute("value"), "dicated to the Public Domain.", + "The first result for the first source doesn't have the correct non-matches in a line."); + + let firstLine1 = searchResult1[0]; + is(firstLine1.querySelector(".dbg-results-line-number").getAttribute("value"), "1", + "The first result for the second source doesn't have the correct line attached."); + + is(firstLine1.querySelectorAll(".dbg-results-line-contents").length, 1, + "The first result for the second source doesn't have the correct number of nodes for a line."); + is(firstLine1.querySelectorAll(".dbg-results-line-contents-string").length, 3, + "The first result for the second source doesn't have the correct number of strings in a line."); + + is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]").length, 1, + "The first result for the second source doesn't have the correct number of matches in a line."); + is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]")[0].getAttribute("value"), "de", + "The first result for the second source doesn't have the correct match in a line."); + + is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]").length, 2, + "The first result for the second source doesn't have the correct number of non-matches in a line."); + is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[0].getAttribute("value"), "/* Any copyright is ", + "The first result for the second source doesn't have the correct non-matches in a line."); + is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[1].getAttribute("value"), "dicated to the Public Domain.", + "The first result for the second source doesn't have the correct non-matches in a line."); + + let secondLine1 = searchResult1[1]; + is(secondLine1.querySelector(".dbg-results-line-number").getAttribute("value"), "6", + "The second result for the second source doesn't have the correct line attached."); + + is(secondLine1.querySelectorAll(".dbg-results-line-contents").length, 1, + "The second result for the second source doesn't have the correct number of nodes for a line."); + is(secondLine1.querySelectorAll(".dbg-results-line-contents-string").length, 3, + "The second result for the second source doesn't have the correct number of strings in a line."); + + is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]").length, 1, + "The second result for the second source doesn't have the correct number of matches in a line."); + is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]")[0].getAttribute("value"), "de", + "The second result for the second source doesn't have the correct match in a line."); + + is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]").length, 2, + "The second result for the second source doesn't have the correct number of non-matches in a line."); + is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[0].getAttribute("value"), " ", + "The second result for the second source doesn't have the correct non-matches in a line."); + is(secondLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[1].getAttribute("value"), "bugger;", + "The second result for the second source doesn't have the correct non-matches in a line."); + + deferred.resolve(); + }); + }); + + setText(gSearchBox, "!de"); + + return deferred.promise; +} + +function secondSearch() { + let deferred = promise.defer(); + + is(gSearchView.itemCount, 2, + "The global search pane should have some child nodes from the previous search."); + is(gSearchView.widget._parent.hidden, false, + "The global search pane should be visible from the previous search."); + is(gSearchView._splitter.hidden, false, + "The global search pane splitter should be visible from the previous search."); + + gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => { + // Some operations are synchronously dispatched on the main thread, + // to avoid blocking UI, thus giving the impression of faster searching. + executeSoon(() => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(isCaretPos(gPanel, 6), + "The editor shouldn't have jumped to a matching line yet."); + ok(getSelectedSourceURL(gSources).includes("-02.js"), + "The current source shouldn't have changed after a global search."); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search."); + + let sourceResults = gDebugger.document.querySelectorAll(".dbg-source-results"); + is(sourceResults.length, 2, + "There should be matches found in two sources."); + + let item0 = gDebugger.SourceResults.getItemForElement(sourceResults[0]); + let item1 = gDebugger.SourceResults.getItemForElement(sourceResults[1]); + is(item0.instance.expanded, true, + "The first source results should automatically be expanded."); + is(item1.instance.expanded, true, + "The second source results should automatically be expanded."); + + let searchResult0 = sourceResults[0].querySelectorAll(".dbg-search-result"); + let searchResult1 = sourceResults[1].querySelectorAll(".dbg-search-result"); + is(searchResult0.length, 1, + "There should be one line result for the first url."); + is(searchResult1.length, 1, + "There should be one line result for the second url."); + + let firstLine0 = searchResult0[0]; + is(firstLine0.querySelector(".dbg-results-line-number").getAttribute("value"), "1", + "The first result for the first source doesn't have the correct line attached."); + + is(firstLine0.querySelectorAll(".dbg-results-line-contents").length, 1, + "The first result for the first source doesn't have the correct number of nodes for a line."); + is(firstLine0.querySelectorAll(".dbg-results-line-contents-string").length, 5, + "The first result for the first source doesn't have the correct number of strings in a line."); + + is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=true]").length, 2, + "The first result for the first source doesn't have the correct number of matches in a line."); + is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=true]")[0].getAttribute("value"), "ed", + "The first result for the first source doesn't have the correct matches in a line."); + is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=true]")[1].getAttribute("value"), "ed", + "The first result for the first source doesn't have the correct matches in a line."); + + is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=false]").length, 3, + "The first result for the first source doesn't have the correct number of non-matches in a line."); + is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=false]")[0].getAttribute("value"), "/* Any copyright is d", + "The first result for the first source doesn't have the correct non-matches in a line."); + is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=false]")[1].getAttribute("value"), "icat", + "The first result for the first source doesn't have the correct non-matches in a line."); + is(firstLine0.querySelectorAll(".dbg-results-line-contents-string[match=false]")[2].getAttribute("value"), " to the Public Domain.", + "The first result for the first source doesn't have the correct non-matches in a line."); + + let firstLine1 = searchResult1[0]; + is(firstLine1.querySelector(".dbg-results-line-number").getAttribute("value"), "1", + "The first result for the second source doesn't have the correct line attached."); + + is(firstLine1.querySelectorAll(".dbg-results-line-contents").length, 1, + "The first result for the second source doesn't have the correct number of nodes for a line."); + is(firstLine1.querySelectorAll(".dbg-results-line-contents-string").length, 5, + "The first result for the second source doesn't have the correct number of strings in a line."); + + is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]").length, 2, + "The first result for the second source doesn't have the correct number of matches in a line."); + is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]")[0].getAttribute("value"), "ed", + "The first result for the second source doesn't have the correct matches in a line."); + is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=true]")[1].getAttribute("value"), "ed", + "The first result for the second source doesn't have the correct matches in a line."); + + is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]").length, 3, + "The first result for the second source doesn't have the correct number of non-matches in a line."); + is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[0].getAttribute("value"), "/* Any copyright is d", + "The first result for the second source doesn't have the correct non-matches in a line."); + is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[1].getAttribute("value"), "icat", + "The first result for the second source doesn't have the correct non-matches in a line."); + is(firstLine1.querySelectorAll(".dbg-results-line-contents-string[match=false]")[2].getAttribute("value"), " to the Public Domain.", + "The first result for the second source doesn't have the correct non-matches in a line."); + + deferred.resolve(); + }); + }); + + backspaceText(gSearchBox, 2); + typeText(gSearchBox, "ED"); + + return deferred.promise; +} + +function clearSearch() { + gSearchView.clearView(); + + is(gSearchView.itemCount, 0, + "The global search pane shouldn't have any child nodes after clearing."); + is(gSearchView.widget._parent.hidden, true, + "The global search pane shouldn't be visible after clearing."); + is(gSearchView._splitter.hidden, true, + "The global search pane splitter shouldn't be visible after clearing."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; + gSearchView = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-02.js new file mode 100644 index 000000000..5713b3822 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-02.js @@ -0,0 +1,203 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the global search results switch back and forth, and wrap around + * when switching between them. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources, gSearchView, gSearchBox; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gSearchView = gDebugger.DebuggerView.GlobalSearch; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + function firstSearch() { + let deferred = promise.defer(); + + is(gSearchView.itemCount, 0, + "The global search pane shouldn't have any entries yet."); + is(gSearchView.widget._parent.hidden, true, + "The global search pane shouldn't be visible yet."); + is(gSearchView._splitter.hidden, true, + "The global search pane splitter shouldn't be visible yet."); + + gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => { + // Some operations are synchronously dispatched on the main thread, + // to avoid blocking UI, thus giving the impression of faster searching. + executeSoon(() => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(isCaretPos(gPanel, 6), + "The editor shouldn't have jumped to a matching line yet."); + ok(getSelectedSourceURL(gSources).includes("-02.js"), + "The current source shouldn't have changed after a global search."); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search."); + + deferred.resolve(); + }); + }); + + setText(gSearchBox, "!function"); + + return deferred.promise; + } + + function doFirstJump() { + let deferred = promise.defer(); + + waitForSourceAndCaret(gPanel, "-01.js", 4).then(() => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(getSelectedSourceURL(gSources).includes("-01.js"), + "The currently shown source is incorrect (1)."); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search (1)."); + + // The editor's selected text takes a tick to update. + ok(isCaretPos(gPanel, 4, 9), + "The editor didn't jump to the correct line (1)."); + is(gEditor.getSelection(), "function", + "The editor didn't select the correct text (1)."); + + deferred.resolve(); + }); + + EventUtils.sendKey("DOWN", gDebugger); + + return deferred.promise; + } + + function doSecondJump() { + let deferred = promise.defer(); + + waitForSourceAndCaret(gPanel, "-02.js", 4).then(() => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(getSelectedSourceURL(gSources).includes("-02.js"), + "The currently shown source is incorrect (2)."); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search (2)."); + + ok(isCaretPos(gPanel, 4, 9), + "The editor didn't jump to the correct line (2)."); + is(gEditor.getSelection(), "function", + "The editor didn't select the correct text (2)."); + + deferred.resolve(); + }); + + EventUtils.sendKey("DOWN", gDebugger); + + return deferred.promise; + } + + function doWrapAroundJump() { + let deferred = promise.defer(); + + waitForSourceAndCaret(gPanel, "-01.js", 4).then(() => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(getSelectedSourceURL(gSources).includes("-01.js"), + "The currently shown source is incorrect (3)."); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search (3)."); + + // The editor's selected text takes a tick to update. + ok(isCaretPos(gPanel, 4, 9), + "The editor didn't jump to the correct line (3)."); + is(gEditor.getSelection(), "function", + "The editor didn't select the correct text (3)."); + + deferred.resolve(); + }); + + EventUtils.sendKey("DOWN", gDebugger); + EventUtils.sendKey("DOWN", gDebugger); + + return deferred.promise; + } + + function doBackwardsWrapAroundJump() { + let deferred = promise.defer(); + + waitForSourceAndCaret(gPanel, "-02.js", 7).then(() => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(getSelectedSourceURL(gSources).includes("-02.js"), + "The currently shown source is incorrect (4)."); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search (4)."); + + // The editor's selected text takes a tick to update. + ok(isCaretPos(gPanel, 7, 11), + "The editor didn't jump to the correct line (4)."); + is(gEditor.getSelection(), "function", + "The editor didn't select the correct text (4)."); + + deferred.resolve(); + }); + + EventUtils.sendKey("UP", gDebugger); + + return deferred.promise; + } + + function testSearchTokenEmpty() { + backspaceText(gSearchBox, 4); + + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(getSelectedSourceURL(gSources).includes("-02.js"), + "The currently shown source is incorrect (4)."); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search (4)."); + ok(isCaretPos(gPanel, 7, 11), + "The editor didn't remain at the correct line (4)."); + is(gEditor.getSelection(), "", + "The editor shouldn't keep the previous text selected (4)."); + + is(gSearchView.itemCount, 0, + "The global search pane shouldn't have any child nodes after clearing."); + is(gSearchView.widget._parent.hidden, true, + "The global search pane shouldn't be visible after clearing."); + is(gSearchView._splitter.hidden, true, + "The global search pane splitter shouldn't be visible after clearing."); + } + + Task.spawn(function* () { + yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1); + yield firstSearch(); + yield doFirstJump(); + yield doSecondJump(); + yield doWrapAroundJump(); + yield doBackwardsWrapAroundJump(); + yield testSearchTokenEmpty(); + resumeDebuggerThenCloseAndFinish(gPanel); + }); + + callInTab(gTab, "firstCall"); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-03.js new file mode 100644 index 000000000..d36f42b59 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-03.js @@ -0,0 +1,110 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the global search results are cleared on location changes, and + * the expected UI behaviors are triggered. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources, gSearchView, gSearchBox; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gSearchView = gDebugger.DebuggerView.GlobalSearch; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1) + .then(firstSearch) + .then(performTest) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + callInTab(gTab, "firstCall"); + }); +} + +function firstSearch() { + let deferred = promise.defer(); + + is(gSearchView.itemCount, 0, + "The global search pane shouldn't have any entries yet."); + is(gSearchView.widget._parent.hidden, true, + "The global search pane shouldn't be visible yet."); + is(gSearchView._splitter.hidden, true, + "The global search pane splitter shouldn't be visible yet."); + + gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => { + // Some operations are synchronously dispatched on the main thread, + // to avoid blocking UI, thus giving the impression of faster searching. + executeSoon(() => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(isCaretPos(gPanel, 6), + "The editor shouldn't have jumped to a matching line yet."); + ok(getSelectedSourceURL(gSources).includes("-02.js"), + "The current source shouldn't have changed after a global search."); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search."); + + deferred.resolve(); + }); + }); + + setText(gSearchBox, "!function"); + + return deferred.promise; +} + +function performTest() { + let deferred = promise.defer(); + + is(gSearchView.itemCount, 2, + "The global search pane should have some entries from the previous search."); + is(gSearchView.widget._parent.hidden, false, + "The global search pane should be visible from the previous search."); + is(gSearchView._splitter.hidden, false, + "The global search pane splitter should be visible from the previous search."); + + reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(() => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + is(gSearchView.itemCount, 0, + "The global search pane shouldn't have any entries after a page navigation."); + is(gSearchView.widget._parent.hidden, true, + "The global search pane shouldn't be visible after a page navigation."); + is(gSearchView._splitter.hidden, true, + "The global search pane splitter shouldn't be visible after a page navigation."); + + deferred.resolve(); + }); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; + gSearchView = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-04.js new file mode 100644 index 000000000..3dde460e4 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-04.js @@ -0,0 +1,98 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the global search results trigger MatchFound and NoMatchFound events + * properly, and triggers the expected UI behavior. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources, gSearchView, gSearchBox; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gSearchView = gDebugger.DebuggerView.GlobalSearch; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1) + .then(firstSearch) + .then(secondSearch) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + callInTab(gTab, "firstCall"); + }); +} + +function firstSearch() { + let deferred = promise.defer(); + + gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => { + // Some operations are synchronously dispatched on the main thread, + // to avoid blocking UI, thus giving the impression of faster searching. + executeSoon(() => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(isCaretPos(gPanel, 6), + "The editor shouldn't have jumped to a matching line yet."); + ok(getSelectedSourceURL(gSources).includes("-02.js"), + "The current source shouldn't have changed after a global search."); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search."); + + deferred.resolve(); + }); + }); + + setText(gSearchBox, "!function"); + + return deferred.promise; +} + +function secondSearch() { + let deferred = promise.defer(); + + gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_NOT_FOUND, () => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(isCaretPos(gPanel, 6), + "The editor shouldn't have jumped to a matching line yet."); + ok(getSelectedSourceURL(gSources).includes("-02.js"), + "The current source shouldn't have changed after a global search."); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search."); + + deferred.resolve(); + }); + + typeText(gSearchBox, "/"); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; + gSearchView = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-05.js new file mode 100644 index 000000000..c441a88ac --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-05.js @@ -0,0 +1,160 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the global search results are expanded/collapsed on click, and + * clicking matches makes the source editor shows the correct source and + * makes a selection based on the match. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources, gSearchView, gSearchBox; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gSearchView = gDebugger.DebuggerView.GlobalSearch; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1) + .then(doSearch) + .then(testExpandCollapse) + .then(testClickLineToJump) + .then(testClickMatchToJump) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + callInTab(gTab, "firstCall"); + }); +} + +function doSearch() { + let deferred = promise.defer(); + + gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => { + // Some operations are synchronously dispatched on the main thread, + // to avoid blocking UI, thus giving the impression of faster searching. + executeSoon(() => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(isCaretPos(gPanel, 6), + "The editor shouldn't have jumped to a matching line yet. (1)"); + ok(getSelectedSourceURL(gSources).includes("-02.js"), + "The current source shouldn't have changed after a global search. (2)"); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search. (3)"); + + deferred.resolve(); + }); + }); + + setText(gSearchBox, "!a"); + + return deferred.promise; +} + +function testExpandCollapse() { + let sourceResults = gDebugger.document.querySelectorAll(".dbg-source-results"); + let item0 = gDebugger.SourceResults.getItemForElement(sourceResults[0]); + let item1 = gDebugger.SourceResults.getItemForElement(sourceResults[1]); + let firstHeader = sourceResults[0].querySelector(".dbg-results-header"); + let secondHeader = sourceResults[1].querySelector(".dbg-results-header"); + + EventUtils.sendMouseEvent({ type: "click" }, firstHeader); + EventUtils.sendMouseEvent({ type: "click" }, secondHeader); + + is(item0.instance.expanded, false, + "The first source results should be collapsed on click. (2)"); + is(item1.instance.expanded, false, + "The second source results should be collapsed on click. (2)"); + + EventUtils.sendMouseEvent({ type: "click" }, firstHeader); + EventUtils.sendMouseEvent({ type: "click" }, secondHeader); + + is(item0.instance.expanded, true, + "The first source results should be expanded on an additional click. (3)"); + is(item1.instance.expanded, true, + "The second source results should be expanded on an additional click. (3)"); +} + +function testClickLineToJump() { + let deferred = promise.defer(); + + let sourceResults = gDebugger.document.querySelectorAll(".dbg-source-results"); + let firstHeader = sourceResults[0].querySelector(".dbg-results-header"); + let firstLine = sourceResults[0].querySelector(".dbg-results-line-contents"); + + waitForSourceAndCaret(gPanel, "-01.js", 1, 1).then(() => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(isCaretPos(gPanel, 1, 5), + "The editor didn't jump to the correct line (4)."); + is(gEditor.getSelection(), "A", + "The editor didn't select the correct text (4)."); + ok(getSelectedSourceURL(gSources).includes("-01.js"), + "The currently shown source is incorrect (4)."); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search (4)."); + + deferred.resolve(); + }); + + EventUtils.sendMouseEvent({ type: "click" }, firstLine); + + return deferred.promise; +} + +function testClickMatchToJump() { + let deferred = promise.defer(); + + let sourceResults = gDebugger.document.querySelectorAll(".dbg-source-results"); + let secondHeader = sourceResults[1].querySelector(".dbg-results-header"); + let secondMatches = sourceResults[1].querySelectorAll(".dbg-results-line-contents-string[match=true]"); + let lastMatch = Array.slice(secondMatches).pop(); + + waitForSourceAndCaret(gPanel, "-02.js", 13, 3).then(() => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(isCaretPos(gPanel, 13, 3), + "The editor didn't jump to the correct line (5)."); + is(gEditor.getSelection(), "a", + "The editor didn't select the correct text (5)."); + ok(getSelectedSourceURL(gSources).includes("-02.js"), + "The currently shown source is incorrect (5)."); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search (5)."); + + deferred.resolve(); + }); + + EventUtils.sendMouseEvent({ type: "click" }, lastMatch); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; + gSearchView = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-06.js new file mode 100644 index 000000000..2de3ac558 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-06.js @@ -0,0 +1,125 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the global search results are hidden when they're supposed to + * (after a focus lost, or when ESCAPE is pressed). + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources, gSearchView, gSearchBox; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gSearchView = gDebugger.DebuggerView.GlobalSearch; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1) + .then(doSearch) + .then(testFocusLost) + .then(doSearch) + .then(testEscape) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + callInTab(gTab, "firstCall"); + }); +} + +function doSearch() { + let deferred = promise.defer(); + + is(gSearchView.itemCount, 0, + "The global search pane shouldn't have any entries yet."); + is(gSearchView.widget._parent.hidden, true, + "The global search pane shouldn't be visible yet."); + is(gSearchView._splitter.hidden, true, + "The global search pane splitter shouldn't be visible yet."); + + gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => { + // Some operations are synchronously dispatched on the main thread, + // to avoid blocking UI, thus giving the impression of faster searching. + executeSoon(() => { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + ok(isCaretPos(gPanel, 6), + "The editor shouldn't have jumped to a matching line yet."); + ok(getSelectedSourceURL(gSources).includes("-02.js"), + "The current source shouldn't have changed after a global search."); + is(gSources.visibleItems.length, 2, + "Not all the sources are shown after the global search."); + + deferred.resolve(); + }); + }); + + setText(gSearchBox, "!a"); + + return deferred.promise; +} + +function testFocusLost() { + is(gSearchView.itemCount, 2, + "The global search pane should have some entries from the previous search."); + is(gSearchView.widget._parent.hidden, false, + "The global search pane should be visible from the previous search."); + is(gSearchView._splitter.hidden, false, + "The global search pane splitter should be visible from the previous search."); + + gDebugger.DebuggerView.editor.focus(); + + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); + + is(gSearchView.itemCount, 0, + "The global search pane shouldn't have any child nodes after clearing."); + is(gSearchView.widget._parent.hidden, true, + "The global search pane shouldn't be visible after clearing."); + is(gSearchView._splitter.hidden, true, + "The global search pane splitter shouldn't be visible after clearing."); +} + +function testEscape() { + is(gSearchView.itemCount, 2, + "The global search pane should have some entries from the previous search."); + is(gSearchView.widget._parent.hidden, false, + "The global search pane should be visible from the previous search."); + is(gSearchView._splitter.hidden, false, + "The global search pane splitter should be visible from the previous search."); + + gSearchBox.focus(); + EventUtils.sendKey("ESCAPE", gDebugger); + + is(gSearchView.itemCount, 0, + "The global search pane shouldn't have any child nodes after clearing."); + is(gSearchView.widget._parent.hidden, true, + "The global search pane shouldn't be visible after clearing."); + is(gSearchView._splitter.hidden, true, + "The global search pane splitter shouldn't be visible after clearing."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; + gSearchView = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-popup-jank.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-popup-jank.js new file mode 100644 index 000000000..317dd6369 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-popup-jank.js @@ -0,0 +1,128 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that sources aren't selected by default when finding a match. + */ + +const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html"; + +var gTab, gPanel, gDebugger; +var gSearchBox; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js?a=b", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + gDebugger.DebuggerView.Filtering.FilteredSources._autoSelectFirstItem = false; + gDebugger.DebuggerView.Filtering.FilteredFunctions._autoSelectFirstItem = false; + + superGenericFileSearch() + .then(() => ensureSourceIs(aPanel, "-01.js")) + .then(() => ensureCaretAt(aPanel, 1)) + + .then(superAccurateFileSearch) + .then(() => ensureSourceIs(aPanel, "-01.js")) + .then(() => ensureCaretAt(aPanel, 1)) + .then(() => pressKeyToHide("RETURN")) + .then(() => ensureSourceIs(aPanel, "code_test-editor-mode", true)) + .then(() => ensureCaretAt(aPanel, 1)) + + .then(superGenericFileSearch) + .then(() => ensureSourceIs(aPanel, "code_test-editor-mode")) + .then(() => ensureCaretAt(aPanel, 1)) + .then(() => { + const shown = waitForSourceShown(aPanel, "doc_editor-mode"); + pressKey("UP"); + return shown; + }) + .then(() => ensureCaretAt(aPanel, 1)) + .then(() => pressKeyToHide("RETURN")) + .then(() => ensureSourceIs(aPanel, "doc_editor-mode")) + .then(() => ensureCaretAt(aPanel, 1)) + + .then(superAccurateFileSearch) + .then(() => ensureSourceIs(aPanel, "doc_editor-mode")) + .then(() => ensureCaretAt(aPanel, 1)) + .then(() => { + const shown = waitForSourceShown(gPanel, "code_test-editor-mode"); + typeText(gSearchBox, ":"); + return shown; + }) + .then(() => ensureSourceIs(aPanel, "code_test-editor-mode", true)) + .then(() => ensureCaretAt(aPanel, 1)) + .then(() => typeText(gSearchBox, "5")) + .then(() => ensureSourceIs(aPanel, "code_test-editor-mode")) + .then(() => ensureCaretAt(aPanel, 5)) + .then(() => pressKey("DOWN")) + .then(() => ensureSourceIs(aPanel, "code_test-editor-mode")) + .then(() => ensureCaretAt(aPanel, 6)) + + .then(superGenericFunctionSearch) + .then(() => ensureSourceIs(aPanel, "code_test-editor-mode")) + .then(() => ensureCaretAt(aPanel, 6)) + .then(() => pressKey("RETURN")) + .then(() => ensureSourceIs(aPanel, "code_test-editor-mode")) + .then(() => ensureCaretAt(aPanel, 4, 10)) + + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function waitForMatchFoundAndResultsShown(aName) { + return promise.all([ + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS[aName]) + ]); +} + +function waitForResultsHidden() { + return once(gDebugger, "popuphidden"); +} + +function superGenericFunctionSearch() { + let finished = waitForMatchFoundAndResultsShown("FUNCTION_SEARCH_MATCH_FOUND"); + setText(gSearchBox, "@"); + return finished; +} + +function superGenericFileSearch() { + let finished = waitForMatchFoundAndResultsShown("FILE_SEARCH_MATCH_FOUND"); + setText(gSearchBox, "."); + return finished; +} + +function superAccurateFileSearch() { + let finished = waitForMatchFoundAndResultsShown("FILE_SEARCH_MATCH_FOUND"); + setText(gSearchBox, "editor"); + return finished; +} + +function pressKey(aKey) { + EventUtils.sendKey(aKey, gDebugger); +} + +function pressKeyToHide(aKey) { + let finished = waitForResultsHidden(); + EventUtils.sendKey(aKey, gDebugger); + return finished; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-01.js new file mode 100644 index 000000000..671f931a7 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-01.js @@ -0,0 +1,232 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests basic functionality of sources filtering (file search). + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(3); + + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + const gSearchView = gDebugger.DebuggerView.Filtering.FilteredSources; + const gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + Task.spawn(function* () { + // move searches to yields + // not sure what to do with the error... + yield bogusSearch(); + yield firstSearch(); + yield secondSearch(); + yield thirdSearch(); + yield fourthSearch(); + yield fifthSearch(); + yield sixthSearch(); + yield seventhSearch(); + + return closeDebuggerAndFinish(gPanel) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); + + function bogusSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_NOT_FOUND) + ]); + + setText(gSearchBox, "BOGUS"); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + verifyContents({ itemCount: 0, hidden: true }) + ])); + } + + function firstSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForSourceShown(gPanel, "-02.js") + ]); + + setText(gSearchBox, "-02.js"); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-02.js"), + ensureCaretAt(gPanel, 1), + verifyContents({ itemCount: 1, hidden: false }) + ])); + } + + function secondSearch() { + let finished = promise.all([ + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForSourceShown(gPanel, "-01.js") + ]) + .then(() => { + let finished = promise.all([ + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForCaretUpdated(gPanel, 5) + ]) + .then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 5), + verifyContents({ itemCount: 1, hidden: false }) + ])); + + typeText(gSearchBox, ":5"); + return finished; + }); + + setText(gSearchBox, ".*-01\.js"); + return finished; + } + + function thirdSearch() { + let finished = promise.all([ + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForSourceShown(gPanel, "-02.js") + ]) + .then(() => { + let finished = promise.all([ + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForCaretUpdated(gPanel, 6, 6) + ]) + .then(() => promise.all([ + ensureSourceIs(gPanel, "-02.js"), + ensureCaretAt(gPanel, 6, 6), + verifyContents({ itemCount: 1, hidden: false }) + ])); + + typeText(gSearchBox, "#deb"); + return finished; + }); + + setText(gSearchBox, ".*-02\.js"); + return finished; + } + + function fourthSearch() { + let finished = promise.all([ + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForSourceShown(gPanel, "-01.js") + ]) + .then(() => { + let finished = promise.all([ + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForCaretUpdated(gPanel, 2, 9), + ]) + .then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 2, 9), + verifyContents({ itemCount: 1, hidden: false }) + // ...because we simply searched for ":" in the current file. + ])); + + typeText(gSearchBox, "#:"); // # has precedence. + return finished; + }); + + setText(gSearchBox, ".*-01\.js"); + return finished; + } + + function fifthSearch() { + let finished = promise.all([ + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForSourceShown(gPanel, "-02.js") + ]) + .then(() => { + let finished = promise.all([ + once(gDebugger, "popuphidden"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_NOT_FOUND), + waitForCaretUpdated(gPanel, 1, 3) + ]) + .then(() => promise.all([ + ensureSourceIs(gPanel, "-02.js"), + ensureCaretAt(gPanel, 1, 3), + verifyContents({ itemCount: 0, hidden: true }) + // ...because the searched label includes ":5", so nothing is found. + ])); + + typeText(gSearchBox, ":5#*"); // # has precedence. + return finished; + }); + + setText(gSearchBox, ".*-02\.js"); + return finished; + } + + function sixthSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-02.js"), + ensureCaretAt(gPanel, 1, 3), + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForCaretUpdated(gPanel, 5) + ]); + + backspaceText(gSearchBox, 2); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-02.js"), + ensureCaretAt(gPanel, 5), + verifyContents({ itemCount: 1, hidden: false }) + ])); + } + + function seventhSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-02.js"), + ensureCaretAt(gPanel, 5), + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForSourceShown(gPanel, "-01.js"), + ]); + + backspaceText(gSearchBox, 6); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 2, 9), + verifyContents({ itemCount: 2, hidden: false }) + ])); + } + + function verifyContents(aArgs) { + is(gSources.visibleItems.length, 2, + "The unmatched sources in the widget should not be hidden."); + is(gSearchView.itemCount, aArgs.itemCount, + "No sources should be displayed in the sources container after a bogus search."); + is(gSearchView.hidden, aArgs.hidden, + "No sources should be displayed in the sources container after a bogus search."); + } + + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-02.js new file mode 100644 index 000000000..3e06a1bdf --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-02.js @@ -0,0 +1,281 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests more complex functionality of sources filtering (file search). + */ + +const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html"; + +var gTab, gPanel, gDebugger; +var gSources, gSourceUtils, gSearchView, gSearchBox; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(3); + + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js?a=b", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + gSourceUtils = gDebugger.SourceUtils; + gSearchView = gDebugger.DebuggerView.Filtering.FilteredSources; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + firstSearch() + .then(secondSearch) + .then(thirdSearch) + .then(fourthSearch) + .then(fifthSearch) + .then(goDown) + .then(goDownAndWrap) + .then(goUpAndWrap) + .then(goUp) + .then(returnAndSwitch) + .then(firstSearch) + .then(clickAndSwitch) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function firstSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND) + ]); + + setText(gSearchBox, "."); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + verifyContents([ + "code_script-switching-01.js?a=b", + "code_test-editor-mode?c=d", + "doc_editor-mode.html" + ]) + ])); +} + +function secondSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND) + ]); + + typeText(gSearchBox, "-0"); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + verifyContents(["code_script-switching-01.js?a=b"]) + ])); +} + +function thirdSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND) + ]); + + backspaceText(gSearchBox, 1); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + verifyContents([ + "code_script-switching-01.js?a=b", + "code_test-editor-mode?c=d", + "doc_editor-mode.html" + ]) + ])); +} + +function fourthSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForSourceShown(gPanel, "test-editor-mode") + ]); + + setText(gSearchBox, "code_test"); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "test-editor-mode"), + ensureCaretAt(gPanel, 1), + verifyContents(["code_test-editor-mode?c=d"]) + ])); +} + +function fifthSearch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "test-editor-mode"), + ensureCaretAt(gPanel, 1), + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND), + waitForSourceShown(gPanel, "-01.js") + ]); + + backspaceText(gSearchBox, 4); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + verifyContents([ + "code_script-switching-01.js?a=b", + "code_test-editor-mode?c=d" + ]) + ])); +} + +function goDown() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + waitForSourceShown(gPanel, "test-editor-mode"), + ]); + + EventUtils.sendKey("DOWN", gDebugger); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "test-editor-mode"), + ensureCaretAt(gPanel, 1), + verifyContents([ + "code_script-switching-01.js?a=b", + "code_test-editor-mode?c=d" + ]) + ])); +} + +function goDownAndWrap() { + let finished = promise.all([ + ensureSourceIs(gPanel, "test-editor-mode"), + ensureCaretAt(gPanel, 1), + waitForSourceShown(gPanel, "-01.js") + ]); + + EventUtils.synthesizeKey("g", { metaKey: true }, gDebugger); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + verifyContents([ + "code_script-switching-01.js?a=b", + "code_test-editor-mode?c=d" + ]) + ])); +} + +function goUpAndWrap() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + waitForSourceShown(gPanel, "test-editor-mode") + ]); + + EventUtils.synthesizeKey("G", { metaKey: true }, gDebugger); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "test-editor-mode"), + ensureCaretAt(gPanel, 1), + verifyContents([ + "code_script-switching-01.js?a=b", + "code_test-editor-mode?c=d" + ]) + ])); +} + +function goUp() { + let finished = promise.all([ + ensureSourceIs(gPanel, "test-editor-mode"), + ensureCaretAt(gPanel, 1), + waitForSourceShown(gPanel, "-01.js"), + ]); + + EventUtils.sendKey("UP", gDebugger); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + verifyContents([ + "code_script-switching-01.js?a=b", + "code_test-editor-mode?c=d" + ]) + ])); +} + +function returnAndSwitch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + once(gDebugger, "popuphidden") + ]); + + EventUtils.sendKey("RETURN", gDebugger); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1) + ])); +} + +function clickAndSwitch() { + let finished = promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1), + once(gDebugger, "popuphidden"), + waitForSourceShown(gPanel, "test-editor-mode") + ]); + + EventUtils.sendMouseEvent({ type: "click" }, gSearchView.items[1].target, gDebugger); + + return finished.then(() => promise.all([ + ensureSourceIs(gPanel, "test-editor-mode"), + ensureCaretAt(gPanel, 1) + ])); +} + +function verifyContents(aMatches) { + is(gSources.visibleItems.length, 3, + "The unmatched sources in the widget should not be hidden."); + is(gSearchView.itemCount, aMatches.length, + "The filtered sources view should have the right items available."); + + for (let i = 0; i < gSearchView.itemCount; i++) { + let trimmedLabel = gSourceUtils.trimUrlLength(gSourceUtils.trimUrlQuery(aMatches[i])); + let trimmedLocation = gSourceUtils.trimUrlLength(EXAMPLE_URL + aMatches[i], 0, "start"); + + ok(gSearchView.widget._parent.querySelector(".results-panel-item-label[value=\"" + trimmedLabel + "\"]"), + "The filtered sources view should have the correct source labels."); + ok(gSearchView.widget._parent.querySelector(".results-panel-item-label-below[value=\"" + trimmedLocation + "\"]"), + "The filtered sources view should have the correct source locations."); + } +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gSources = null; + gSourceUtils = null; + gSearchView = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-03.js new file mode 100644 index 000000000..904a51f76 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-03.js @@ -0,0 +1,103 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that while searching for files, the sources list remains unchanged. + */ + +const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html"; + +var gTab, gPanel, gDebugger; +var gSources, gSearchBox; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js?a=b", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + superGenericSearch() + .then(verifySourcesPane) + .then(kindaInterpretableSearch) + .then(verifySourcesPane) + .then(incrediblySpecificSearch) + .then(verifySourcesPane) + .then(returnAndHide) + .then(verifySourcesPane) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function waitForMatchFoundAndResultsShown() { + return promise.all([ + once(gDebugger, "popupshown"), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND) + ]).then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1) + ])); +} + +function waitForResultsHidden() { + return once(gDebugger, "popuphidden").then(() => promise.all([ + ensureSourceIs(gPanel, "-01.js"), + ensureCaretAt(gPanel, 1) + ])); +} + +function superGenericSearch() { + let finished = waitForMatchFoundAndResultsShown(); + setText(gSearchBox, "."); + return finished; +} + +function kindaInterpretableSearch() { + let finished = waitForMatchFoundAndResultsShown(); + typeText(gSearchBox, "-0"); + return finished; +} + +function incrediblySpecificSearch() { + let finished = waitForMatchFoundAndResultsShown(); + typeText(gSearchBox, "1.js"); + return finished; +} + +function returnAndHide() { + let finished = waitForResultsHidden(); + EventUtils.sendKey("RETURN", gDebugger); + return finished; +} + +function verifySourcesPane() { + is(gSources.itemCount, 3, + "There should be 3 items present in the sources container."); + is(gSources.visibleItems.length, 3, + "There should be no hidden items in the sources container."); + + ok(gSources.getItemForAttachment(e => e.label == "code_script-switching-01.js"), + "The first source's label should be correct."); + ok(gSources.getItemForAttachment(e => e.label == "code_test-editor-mode"), + "The second source's label should be correct."); + ok(gSources.getItemForAttachment(e => e.label == "doc_editor-mode.html"), + "The third source's label should be correct."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gSources = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_search-symbols.js b/devtools/client/debugger/test/mochitest/browser_dbg_search-symbols.js new file mode 100644 index 000000000..5998acf8e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-symbols.js @@ -0,0 +1,472 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the function searching works properly. + */ + +const TAB_URL = EXAMPLE_URL + "doc_function-search.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources, gSearchBox, gFilteredFunctions; + +function test() { + let options = { + source: EXAMPLE_URL + "code_function-search-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + gFilteredFunctions = gDebugger.DebuggerView.Filtering.FilteredFunctions; + + showSource("doc_function-search.html") + .then(htmlSearch) + .then(() => showSource("code_function-search-01.js")) + .then(firstJsSearch) + .then(() => showSource("code_function-search-02.js")) + .then(secondJsSearch) + .then(() => showSource("code_function-search-03.js")) + .then(thirdJsSearch) + .then(saveSearch) + .then(filterSearch) + .then(bogusSearch) + .then(incrementalSearch) + .then(emptySearch) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function htmlSearch() { + let deferred = promise.defer(); + + once(gDebugger, "popupshown").then(() => { + writeInfo(); + + is(gFilteredFunctions.selectedIndex, 0, + "An item should be selected in the filtered functions view (1)."); + ok(gFilteredFunctions.selectedItem, + "An item should be selected in the filtered functions view (2)."); + + if (gSources.selectedItem.attachment.source.url.indexOf(".html") != -1) { + let expectedResults = [ + ["inline", ".html", "", 19, 16], + ["arrow", ".html", "", 20, 11], + ["foo", ".html", "", 22, 11], + ["foo2", ".html", "", 23, 11], + ["bar2", ".html", "", 23, 18] + ]; + + for (let [label, value, description, line, column] of expectedResults) { + let target = gFilteredFunctions.selectedItem.target; + + if (label) { + is(target.querySelector(".results-panel-item-label").getAttribute("value"), + gDebugger.SourceUtils.trimUrlLength(label + "()"), + "The correct label (" + label + ") is currently selected."); + } else { + ok(!target.querySelector(".results-panel-item-label"), + "Shouldn't create empty label nodes."); + } + if (value) { + ok(target.querySelector(".results-panel-item-label-below").getAttribute("value").includes(value), + "The correct value (" + value + ") is attached."); + } else { + ok(!target.querySelector(".results-panel-item-label-below"), + "Shouldn't create empty label nodes."); + } + if (description) { + is(target.querySelector(".results-panel-item-label-before").getAttribute("value"), description, + "The correct description (" + description + ") is currently shown."); + } else { + ok(!target.querySelector(".results-panel-item-label-before"), + "Shouldn't create empty label nodes."); + } + + ok(isCaretPos(gPanel, line, column), + "The editor didn't jump to the correct line."); + + EventUtils.sendKey("DOWN", gDebugger); + } + + ok(isCaretPos(gPanel, expectedResults[0][3], expectedResults[0][4]), + "The editor didn't jump to the correct line again."); + + deferred.resolve(); + } else { + ok(false, "How did you get here? Go away, you."); + } + }); + + setText(gSearchBox, "@"); + return deferred.promise; +} + +function firstJsSearch() { + let deferred = promise.defer(); + + once(gDebugger, "popupshown").then(() => { + writeInfo(); + + is(gFilteredFunctions.selectedIndex, 0, + "An item should be selected in the filtered functions view (1)."); + ok(gFilteredFunctions.selectedItem, + "An item should be selected in the filtered functions view (2)."); + + if (gSources.selectedItem.attachment.source.url.indexOf("-01.js") != -1) { + let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " "; + let expectedResults = [ + ["test", "-01.js", "", 4, 10], + ["anonymousExpression", "-01.js", "test.prototype", 9, 3], + ["namedExpression" + s + "NAME", "-01.js", "test.prototype", 11, 3], + ["a_test", "-01.js", "foo", 22, 3], + ["n_test" + s + "x", "-01.js", "foo", 24, 3], + ["a_test", "-01.js", "foo.sub", 27, 5], + ["n_test" + s + "y", "-01.js", "foo.sub", 29, 5], + ["a_test", "-01.js", "foo.sub.sub", 32, 7], + ["n_test" + s + "z", "-01.js", "foo.sub.sub", 34, 7], + ["test_SAME_NAME", "-01.js", "foo.sub.sub.sub", 37, 9] + ]; + + for (let [label, value, description, line, column] of expectedResults) { + let target = gFilteredFunctions.selectedItem.target; + + if (label) { + is(target.querySelector(".results-panel-item-label").getAttribute("value"), + gDebugger.SourceUtils.trimUrlLength(label + "()"), + "The correct label (" + label + ") is currently selected."); + } else { + ok(!target.querySelector(".results-panel-item-label"), + "Shouldn't create empty label nodes."); + } + if (value) { + ok(target.querySelector(".results-panel-item-label-below").getAttribute("value").includes(value), + "The correct value (" + value + ") is attached."); + } else { + ok(!target.querySelector(".results-panel-item-label-below"), + "Shouldn't create empty label nodes."); + } + if (description) { + is(target.querySelector(".results-panel-item-label-before").getAttribute("value"), description, + "The correct description (" + description + ") is currently shown."); + } else { + ok(!target.querySelector(".results-panel-item-label-before"), + "Shouldn't create empty label nodes."); + } + + ok(isCaretPos(gPanel, line, column), + "The editor didn't jump to the correct line."); + + EventUtils.sendKey("DOWN", gDebugger); + } + + ok(isCaretPos(gPanel, expectedResults[0][3], expectedResults[0][4]), + "The editor didn't jump to the correct line again."); + + deferred.resolve(); + } else { + ok(false, "How did you get here? Go away, you."); + } + }); + + setText(gSearchBox, "@"); + return deferred.promise; +} + +function secondJsSearch() { + let deferred = promise.defer(); + + once(gDebugger, "popupshown").then(() => { + writeInfo(); + + is(gFilteredFunctions.selectedIndex, 0, + "An item should be selected in the filtered functions view (1)."); + ok(gFilteredFunctions.selectedItem, + "An item should be selected in the filtered functions view (2)."); + + if (gSources.selectedItem.attachment.source.url.indexOf("-02.js") != -1) { + let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " "; + let expectedResults = [ + ["test2", "-02.js", "", 4, 5], + ["test3" + s + "test3_NAME", "-02.js", "", 8, 5], + ["test4_SAME_NAME", "-02.js", "", 11, 5], + ["x" + s + "X", "-02.js", "test.prototype", 14, 1], + ["y" + s + "Y", "-02.js", "test.prototype.sub", 16, 1], + ["z" + s + "Z", "-02.js", "test.prototype.sub.sub", 18, 1], + ["t", "-02.js", "test.prototype.sub.sub.sub", 20, 1], + ["x", "-02.js", "this", 20, 32], + ["y", "-02.js", "this", 20, 41], + ["z", "-02.js", "this", 20, 50] + ]; + + for (let [label, value, description, line, column] of expectedResults) { + let target = gFilteredFunctions.selectedItem.target; + + if (label) { + is(target.querySelector(".results-panel-item-label").getAttribute("value"), + gDebugger.SourceUtils.trimUrlLength(label + "()"), + "The correct label (" + label + ") is currently selected."); + } else { + ok(!target.querySelector(".results-panel-item-label"), + "Shouldn't create empty label nodes."); + } + if (value) { + ok(target.querySelector(".results-panel-item-label-below").getAttribute("value").includes(value), + "The correct value (" + value + ") is attached."); + } else { + ok(!target.querySelector(".results-panel-item-label-below"), + "Shouldn't create empty label nodes."); + } + if (description) { + is(target.querySelector(".results-panel-item-label-before").getAttribute("value"), description, + "The correct description (" + description + ") is currently shown."); + } else { + ok(!target.querySelector(".results-panel-item-label-before"), + "Shouldn't create empty label nodes."); + } + + ok(isCaretPos(gPanel, line, column), + "The editor didn't jump to the correct line."); + + EventUtils.sendKey("DOWN", gDebugger); + } + + ok(isCaretPos(gPanel, expectedResults[0][3], expectedResults[0][4]), + "The editor didn't jump to the correct line again."); + + deferred.resolve(); + } else { + ok(false, "How did you get here? Go away, you."); + } + }); + + setText(gSearchBox, "@"); + return deferred.promise; +} + +function thirdJsSearch() { + let deferred = promise.defer(); + + once(gDebugger, "popupshown").then(() => { + writeInfo(); + + is(gFilteredFunctions.selectedIndex, 0, + "An item should be selected in the filtered functions view (1)."); + ok(gFilteredFunctions.selectedItem, + "An item should be selected in the filtered functions view (2)."); + + if (gSources.selectedItem.attachment.source.url.indexOf("-03.js") != -1) { + let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " "; + let expectedResults = [ + ["namedEventListener", "-03.js", "", 4, 43], + ["a" + s + "A", "-03.js", "bar", 10, 5], + ["b" + s + "B", "-03.js", "bar.alpha", 15, 5], + ["c" + s + "C", "-03.js", "bar.alpha.beta", 20, 5], + ["d" + s + "D", "-03.js", "this.theta", 25, 5], + ["fun", "-03.js", "", 29, 7], + ["foo", "-03.js", "", 29, 13], + ["bar", "-03.js", "", 29, 19], + ["t_foo", "-03.js", "this", 29, 25], + ["w_bar" + s + "baz", "-03.js", "window", 29, 38] + ]; + + for (let [label, value, description, line, column] of expectedResults) { + let target = gFilteredFunctions.selectedItem.target; + + if (label) { + is(target.querySelector(".results-panel-item-label").getAttribute("value"), + gDebugger.SourceUtils.trimUrlLength(label + "()"), + "The correct label (" + label + ") is currently selected."); + } else { + ok(!target.querySelector(".results-panel-item-label"), + "Shouldn't create empty label nodes."); + } + if (value) { + ok(target.querySelector(".results-panel-item-label-below").getAttribute("value").includes(value), + "The correct value (" + value + ") is attached."); + } else { + ok(!target.querySelector(".results-panel-item-label-below"), + "Shouldn't create empty label nodes."); + } + if (description) { + is(target.querySelector(".results-panel-item-label-before").getAttribute("value"), description, + "The correct description (" + description + ") is currently shown."); + } else { + ok(!target.querySelector(".results-panel-item-label-before"), + "Shouldn't create empty label nodes."); + } + + ok(isCaretPos(gPanel, line, column), + "The editor didn't jump to the correct line."); + + EventUtils.sendKey("DOWN", gDebugger); + } + + ok(isCaretPos(gPanel, expectedResults[0][3], expectedResults[0][4]), + "The editor didn't jump to the correct line again."); + + deferred.resolve(); + } else { + ok(false, "How did you get here? Go away, you."); + } + }); + + setText(gSearchBox, "@"); + return deferred.promise; +} + +function filterSearch() { + let deferred = promise.defer(); + + once(gDebugger, "popupshown").then(() => { + writeInfo(); + + is(gFilteredFunctions.selectedIndex, 0, + "An item should be selected in the filtered functions view (1)."); + ok(gFilteredFunctions.selectedItem, + "An item should be selected in the filtered functions view (2)."); + + if (gSources.selectedItem.attachment.source.url.indexOf("-03.js") != -1) { + let s = " " + gDebugger.L10N.getStr("functionSearchSeparatorLabel") + " "; + let expectedResults = [ + ["namedEventListener", "-03.js", "", 4, 43], + ["bar", "-03.js", "", 29, 19], + ["w_bar" + s + "baz", "-03.js", "window", 29, 38], + ["anonymousExpression", "-01.js", "test.prototype", 9, 3], + ["namedExpression" + s + "NAME", "-01.js", "test.prototype", 11, 3], + ["arrow", ".html", "", 20, 11], + ["bar2", ".html", "", 23, 18] + ]; + + for (let [label, value, description, line, column] of expectedResults) { + let target = gFilteredFunctions.selectedItem.target; + + if (label) { + is(target.querySelector(".results-panel-item-label").getAttribute("value"), + gDebugger.SourceUtils.trimUrlLength(label + "()"), + "The correct label (" + label + ") is currently selected."); + } else { + ok(!target.querySelector(".results-panel-item-label"), + "Shouldn't create empty label nodes."); + } + if (value) { + ok(target.querySelector(".results-panel-item-label-below").getAttribute("value").includes(value), + "The correct value (" + value + ") is attached."); + } else { + ok(!target.querySelector(".results-panel-item-label-below"), + "Shouldn't create empty label nodes."); + } + if (description) { + is(target.querySelector(".results-panel-item-label-before").getAttribute("value"), description, + "The correct description (" + description + ") is currently shown."); + } else { + ok(!target.querySelector(".results-panel-item-label-before"), + "Shouldn't create empty label nodes."); + } + + ok(isCaretPos(gPanel, line, column), + "The editor didn't jump to the correct line."); + + EventUtils.sendKey("DOWN", gDebugger); + } + + ok(isCaretPos(gPanel, expectedResults[0][3], expectedResults[0][4]), + "The editor didn't jump to the correct line again."); + + deferred.resolve(); + } else { + ok(false, "How did you get here? Go away, you."); + } + }); + + setText(gSearchBox, "@r"); + return deferred.promise; +} + +function bogusSearch() { + let deferred = promise.defer(); + + once(gDebugger, "popuphidden").then(() => { + ok(true, "Popup was successfully hidden after no matches were found."); + deferred.resolve(); + }); + + setText(gSearchBox, "@bogus"); + return deferred.promise; +} + +function incrementalSearch() { + let deferred = promise.defer(); + + once(gDebugger, "popupshown").then(() => { + ok(true, "Popup was successfully shown after some matches were found."); + deferred.resolve(); + }); + + setText(gSearchBox, "@NAME"); + return deferred.promise; +} + +function emptySearch() { + let deferred = promise.defer(); + + once(gDebugger, "popuphidden").then(() => { + ok(true, "Popup was successfully hidden when nothing was searched."); + deferred.resolve(); + }); + + clearText(gSearchBox); + return deferred.promise; +} + +function showSource(aLabel) { + let deferred = promise.defer(); + + waitForSourceShown(gPanel, aLabel).then(deferred.resolve); + gSources.selectedItem = e => e.attachment.label == aLabel; + + return deferred.promise; +} + +function saveSearch() { + let finished = once(gDebugger, "popuphidden"); + + // Either by pressing RETURN or clicking on an item in the popup, + // the popup should hide and the item should become selected. + let random = Math.random(); + if (random >= 0.33) { + EventUtils.sendKey("RETURN", gDebugger); + } else if (random >= 0.66) { + EventUtils.sendKey("RETURN", gDebugger); + } else { + EventUtils.sendMouseEvent({ type: "click" }, + gFilteredFunctions.selectedItem.target, + gDebugger); + } + + return finished; +} + +function writeInfo() { + info("Current source url:\n" + getSelectedSourceURL(gSources)); + info("Debugger editor text:\n" + gEditor.getText()); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; + gSearchBox = null; + gFilteredFunctions = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-01.js new file mode 100644 index 000000000..6276567c2 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-01.js @@ -0,0 +1,64 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the searchbox popup is displayed when focusing the searchbox, + * and hidden when the user starts typing. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +var gTab, gPanel, gDebugger; +var gSearchBox, gSearchBoxPanel; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + gSearchBoxPanel = gDebugger.DebuggerView.Filtering._searchboxHelpPanel; + + waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1) + .then(showPopup) + .then(hidePopup) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + callInTab(gTab, "firstCall"); + }); +} + +function showPopup() { + is(gSearchBoxPanel.state, "closed", + "The search box panel shouldn't be visible yet."); + + let finished = once(gSearchBoxPanel, "popupshown"); + EventUtils.sendMouseEvent({ type: "click" }, gSearchBox, gDebugger); + return finished; +} + +function hidePopup() { + is(gSearchBoxPanel.state, "open", + "The search box panel should be visible after searching started."); + + let finished = once(gSearchBoxPanel, "popuphidden"); + setText(gSearchBox, "#"); + return finished; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gSearchBox = null; + gSearchBoxPanel = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-02.js new file mode 100644 index 000000000..277dd0bc4 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-02.js @@ -0,0 +1,90 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the searchbox popup isn't displayed when there's some text + * already present. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSearchBox, gSearchBoxPanel; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + gSearchBoxPanel = gDebugger.DebuggerView.Filtering._searchboxHelpPanel; + + once(gSearchBoxPanel, "popupshown").then(() => { + ok(false, "Damn it, this shouldn't have happened."); + }); + + waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1) + .then(tryShowPopup) + .then(focusEditor) + .then(testFocusLost) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + callInTab(gTab, "firstCall"); + }); +} + +function tryShowPopup() { + setText(gSearchBox, "#call()"); + ok(isCaretPos(gPanel, 4, 22), + "The editor caret position appears to be correct."); + ok(isEditorSel(gPanel, [125, 131]), + "The editor selection appears to be correct."); + is(gEditor.getSelection(), "Call()", + "The editor selected text appears to be correct."); + + is(gSearchBoxPanel.state, "closed", + "The search box panel shouldn't be visible yet."); + + EventUtils.sendMouseEvent({ type: "click" }, gSearchBox, gDebugger); +} + +function focusEditor() { + let deferred = promise.defer(); + + // Focusing the editor takes a tick to update the caret and selection. + gEditor.focus(); + executeSoon(deferred.resolve); + + return deferred.promise; +} + +function testFocusLost() { + ok(isCaretPos(gPanel, 4, 22), + "The editor caret position appears to be correct after gaining focus."); + ok(isEditorSel(gPanel, [125, 131]), + "The editor selection appears to be correct after gaining focus."); + is(gEditor.getSelection(), "Call()", + "The editor selected text appears to be correct after gaining focus."); + + is(gSearchBoxPanel.state, "closed", + "The search box panel should still not be visible."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSearchBox = null; + gSearchBoxPanel = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-parse.js b/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-parse.js new file mode 100644 index 000000000..2bb8e4150 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-parse.js @@ -0,0 +1,126 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that text entered in the debugger's searchbox is properly parsed. + */ + +function test() { + initDebugger().then(([aTab,, aPanel]) => { + let filterView = aPanel.panelWin.DebuggerView.Filtering; + let searchbox = aPanel.panelWin.DebuggerView.Filtering._searchbox; + + setText(searchbox, ""); + is(filterView.searchData.toSource(), '["", [""]]', + "The searchbox data wasn't parsed correctly (1)."); + + setText(searchbox, "#token"); + is(filterView.searchData.toSource(), '["#", ["", "token"]]', + "The searchbox data wasn't parsed correctly (2)."); + + setText(searchbox, ":42"); + is(filterView.searchData.toSource(), '[":", ["", 42]]', + "The searchbox data wasn't parsed correctly (3)."); + + setText(searchbox, "#token:42"); + is(filterView.searchData.toSource(), '["#", ["", "token:42"]]', + "The searchbox data wasn't parsed correctly (4)."); + + setText(searchbox, ":42#token"); + is(filterView.searchData.toSource(), '["#", [":42", "token"]]', + "The searchbox data wasn't parsed correctly (5)."); + + setText(searchbox, "#token:42#token:42"); + is(filterView.searchData.toSource(), '["#", ["#token:42", "token:42"]]', + "The searchbox data wasn't parsed correctly (6)."); + + setText(searchbox, ":42#token:42#token"); + is(filterView.searchData.toSource(), '["#", [":42#token:42", "token"]]', + "The searchbox data wasn't parsed correctly (7)."); + + + setText(searchbox, "file"); + is(filterView.searchData.toSource(), '["", ["file"]]', + "The searchbox data wasn't parsed correctly (8)."); + + setText(searchbox, "file#token"); + is(filterView.searchData.toSource(), '["#", ["file", "token"]]', + "The searchbox data wasn't parsed correctly (9)."); + + setText(searchbox, "file:42"); + is(filterView.searchData.toSource(), '[":", ["file", 42]]', + "The searchbox data wasn't parsed correctly (10)."); + + setText(searchbox, "file#token:42"); + is(filterView.searchData.toSource(), '["#", ["file", "token:42"]]', + "The searchbox data wasn't parsed correctly (11)."); + + setText(searchbox, "file:42#token"); + is(filterView.searchData.toSource(), '["#", ["file:42", "token"]]', + "The searchbox data wasn't parsed correctly (12)."); + + setText(searchbox, "file#token:42#token:42"); + is(filterView.searchData.toSource(), '["#", ["file#token:42", "token:42"]]', + "The searchbox data wasn't parsed correctly (13)."); + + setText(searchbox, "file:42#token:42#token"); + is(filterView.searchData.toSource(), '["#", ["file:42#token:42", "token"]]', + "The searchbox data wasn't parsed correctly (14)."); + + + setText(searchbox, "!token"); + is(filterView.searchData.toSource(), '["!", ["token"]]', + "The searchbox data wasn't parsed correctly (15)."); + + setText(searchbox, "!token#global"); + is(filterView.searchData.toSource(), '["!", ["token#global"]]', + "The searchbox data wasn't parsed correctly (16)."); + + setText(searchbox, "!token#global:42"); + is(filterView.searchData.toSource(), '["!", ["token#global:42"]]', + "The searchbox data wasn't parsed correctly (17)."); + + setText(searchbox, "!token:42#global"); + is(filterView.searchData.toSource(), '["!", ["token:42#global"]]', + "The searchbox data wasn't parsed correctly (18)."); + + + setText(searchbox, "@token"); + is(filterView.searchData.toSource(), '["@", ["token"]]', + "The searchbox data wasn't parsed correctly (19)."); + + setText(searchbox, "@token#global"); + is(filterView.searchData.toSource(), '["@", ["token#global"]]', + "The searchbox data wasn't parsed correctly (20)."); + + setText(searchbox, "@token#global:42"); + is(filterView.searchData.toSource(), '["@", ["token#global:42"]]', + "The searchbox data wasn't parsed correctly (21)."); + + setText(searchbox, "@token:42#global"); + is(filterView.searchData.toSource(), '["@", ["token:42#global"]]', + "The searchbox data wasn't parsed correctly (22)."); + + + setText(searchbox, "*token"); + is(filterView.searchData.toSource(), '["*", ["token"]]', + "The searchbox data wasn't parsed correctly (23)."); + + setText(searchbox, "*token#global"); + is(filterView.searchData.toSource(), '["*", ["token#global"]]', + "The searchbox data wasn't parsed correctly (24)."); + + setText(searchbox, "*token#global:42"); + is(filterView.searchData.toSource(), '["*", ["token#global:42"]]', + "The searchbox data wasn't parsed correctly (25)."); + + setText(searchbox, "*token:42#global"); + is(filterView.searchData.toSource(), '["*", ["token:42#global"]]', + "The searchbox data wasn't parsed correctly (26)."); + + + closeDebuggerAndFinish(aPanel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-01.js new file mode 100644 index 000000000..b318d8798 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-01.js @@ -0,0 +1,218 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test adding conditional breakpoints (with server-side support) + */ + +const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html"; + +function test() { + // Linux debug test slaves are a bit slow at this test sometimes. + requestLongerTimeout(2); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + const addBreakpoints = Task.async(function* () { + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 18 }, + "undefined"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 19 }, + "null"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 20 }, + "42"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 21 }, + "true"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 22 }, + "'nasu'"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 23 }, + "/regexp/"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 24 }, + "({})"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 25 }, + "(function() {})"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 26 }, + "(function() { return false; })()"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 27 }, + "a"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 28 }, + "a !== undefined"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 29 }, + "b"); + yield actions.addBreakpoint({ actor: gSources.selectedValue, line: 30 }, + "a !== null"); + }); + + function resumeAndTestBreakpoint(line) { + let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line)); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + + return finished; + } + + function resumeAndTestNoBreakpoint() { + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => { + is(gSources.itemCount, 1, + "Found the expected number of sources."); + is(gEditor.getText().indexOf("ermahgerd"), 253, + "The correct source was loaded initially."); + is(gSources.selectedValue, gSources.values[0], + "The correct source is selected."); + + ok(gSources.selectedItem, + "There should be a selected source in the sources pane."); + ok(!gSources._selectedBreakpoint, + "There should be no selected breakpoint in the sources pane."); + is(gSources._conditionalPopupVisible, false, + "The breakpoint conditional expression popup should not be shown."); + + is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 0, + "There should be no visible stackframes."); + is(gDebugger.document.querySelectorAll(".dbg-breakpoint").length, 13, + "There should be thirteen visible breakpoints."); + }); + + gDebugger.gThreadClient.resume(); + + return finished; + } + + function testBreakpoint(line, highlightBreakpoint) { + // Highlight the breakpoint only if required. + if (highlightBreakpoint) { + let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line)); + gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: line }); + return finished; + } + + let selectedActor = gSources.selectedValue; + let selectedBreakpoint = gSources._selectedBreakpoint; + let selectedBreakpointItem = gSources._getBreakpoint(selectedBreakpoint); + + ok(selectedActor, + "There should be a selected item in the sources pane."); + ok(selectedBreakpoint, + "There should be a selected breakpoint in the sources pane."); + + let source = gSources.selectedItem.attachment.source; + let bp = queries.getBreakpoint(getState(), selectedBreakpoint.location); + + ok(bp, "The selected breakpoint exists"); + is(bp.location.actor, source.actor, + "The breakpoint on line " + line + " wasn't added on the correct source."); + is(bp.location.line, line, + "The breakpoint on line " + line + " wasn't found."); + is(!!bp.disabled, false, + "The breakpoint on line " + line + " should be enabled."); + is(!!selectedBreakpointItem.attachment.openPopup, false, + "The breakpoint on line " + line + " should not have opened a popup."); + is(gSources._conditionalPopupVisible, false, + "The breakpoint conditional expression popup should not have been shown."); + isnot(bp.condition, undefined, + "The breakpoint on line " + line + " should have a conditional expression."); + ok(isCaretPos(gPanel, line), + "The editor caret position is not properly set."); + } + + const testAfterReload = Task.async(function* () { + let selectedActor = gSources.selectedValue; + let selectedBreakpoint = gSources._selectedBreakpoint; + + ok(selectedActor, + "There should be a selected item in the sources pane after reload."); + ok(!selectedBreakpoint, + "There should be no selected breakpoint in the sources pane after reload."); + + yield testBreakpoint(18, true); + yield testBreakpoint(19, true); + yield testBreakpoint(20, true); + yield testBreakpoint(21, true); + yield testBreakpoint(22, true); + yield testBreakpoint(23, true); + yield testBreakpoint(24, true); + yield testBreakpoint(25, true); + yield testBreakpoint(26, true); + yield testBreakpoint(27, true); + yield testBreakpoint(28, true); + yield testBreakpoint(29, true); + yield testBreakpoint(30, true); + + is(gSources.itemCount, 1, + "Found the expected number of sources."); + is(gEditor.getText().indexOf("ermahgerd"), 253, + "The correct source was loaded again."); + is(gSources.selectedValue, gSources.values[0], + "The correct source is selected."); + + ok(gSources.selectedItem, + "There should be a selected source in the sources pane."); + ok(gSources._selectedBreakpoint, + "There should be a selected breakpoint in the sources pane."); + is(gSources._conditionalPopupVisible, false, + "The breakpoint conditional expression popup should not be shown."); + }); + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretAndScopes(gPanel, 17); + callInTab(gTab, "ermahgerd"); + yield onCaretUpdated; + + yield addBreakpoints(); + + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(queries.getSourceCount(getState()), 1, + "Found the expected number of sources."); + is(gEditor.getText().indexOf("ermahgerd"), 253, + "The correct source was loaded initially."); + is(gSources.selectedValue, gSources.values[0], + "The correct source is selected."); + is(queries.getBreakpoints(getState()).length, 13, + "13 breakpoints currently added."); + + yield resumeAndTestBreakpoint(20); + yield resumeAndTestBreakpoint(21); + yield resumeAndTestBreakpoint(22); + yield resumeAndTestBreakpoint(23); + yield resumeAndTestBreakpoint(24); + yield resumeAndTestBreakpoint(25); + yield resumeAndTestBreakpoint(27); + yield resumeAndTestBreakpoint(28); + yield resumeAndTestBreakpoint(29); + yield resumeAndTestBreakpoint(30); + yield resumeAndTestNoBreakpoint(); + + let sourceShown = waitForSourceShown(gPanel, ".html"); + reload(gPanel), + yield sourceShown; + testAfterReload(); + + // When a breakpoint is highlighted, the conditional expression + // popup opens, and then closes, and when it closes it sends the + // expression to the server which pauses the server. Make sure + // we wait if there is a pending request. + if (gDebugger.gThreadClient.state === "paused") { + yield waitForThreadEvents(gPanel, "resumed"); + } + + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-02.js new file mode 100644 index 000000000..31f2af36c --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-02.js @@ -0,0 +1,214 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test adding and modifying conditional breakpoints (with server-side support) + */ + +const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + const CONDITIONAL_POPUP_SHOWN = gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWN; + + function addBreakpoint1() { + return actions.addBreakpoint({ actor: gSources.selectedValue, line: 18 }); + } + + function addBreakpoint2() { + let finished = waitForDispatch(gPanel, constants.ADD_BREAKPOINT); + setCaretPosition(19); + gSources._onCmdAddBreakpoint(); + return finished; + } + + function modBreakpoint2() { + setCaretPosition(19); + + let popupShown = waitForDebuggerEvents(gPanel, CONDITIONAL_POPUP_SHOWN); + gSources._onCmdAddConditionalBreakpoint(); + return popupShown; + } + + function* addBreakpoint3() { + let finished = waitForDispatch(gPanel, constants.ADD_BREAKPOINT); + let popupShown = waitForDebuggerEvents(gPanel, CONDITIONAL_POPUP_SHOWN); + + setCaretPosition(20); + gSources._onCmdAddConditionalBreakpoint(); + yield finished; + yield popupShown; + } + + function* modBreakpoint3() { + setCaretPosition(20); + + let popupShown = waitForDebuggerEvents(gPanel, CONDITIONAL_POPUP_SHOWN); + gSources._onCmdAddConditionalBreakpoint(); + yield popupShown; + + typeText(gSources._cbTextbox, "bamboocha"); + + let finished = waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION); + EventUtils.sendKey("RETURN", gDebugger); + yield finished; + } + + function addBreakpoint4() { + let finished = waitForDispatch(gPanel, constants.ADD_BREAKPOINT); + setCaretPosition(21); + gSources._onCmdAddBreakpoint(); + return finished; + } + + function delBreakpoint4() { + let finished = waitForDispatch(gPanel, constants.REMOVE_BREAKPOINT); + setCaretPosition(21); + gSources._onCmdAddBreakpoint(); + return finished; + } + + function testBreakpoint(aLine, aPopupVisible, aConditionalExpression) { + const source = queries.getSelectedSource(getState()); + ok(source, + "There should be a selected item in the sources pane."); + + const bp = queries.getBreakpoint(getState(), { + actor: source.actor, + line: aLine + }); + const bpItem = gSources._getBreakpoint(bp); + ok(bp, "There should be a breakpoint."); + ok(bpItem, "There should be a breakpoint in the sources pane."); + + is(bp.location.actor, source.actor, + "The breakpoint on line " + aLine + " wasn't added on the correct source."); + is(bp.location.line, aLine, + "The breakpoint on line " + aLine + " wasn't found."); + is(!!bp.disabled, false, + "The breakpoint on line " + aLine + " should be enabled."); + is(gSources._conditionalPopupVisible, aPopupVisible, + "The breakpoint on line " + aLine + " should have a correct popup state (2)."); + is(bp.condition, aConditionalExpression, + "The breakpoint on line " + aLine + " should have a correct conditional expression."); + } + + function testNoBreakpoint(aLine) { + let selectedActor = gSources.selectedValue; + let selectedBreakpoint = gSources._selectedBreakpoint; + + ok(selectedActor, + "There should be a selected item in the sources pane for line " + aLine + "."); + ok(!selectedBreakpoint, + "There should be no selected brekapoint in the sources pane for line " + aLine + "."); + + ok(isCaretPos(gPanel, aLine), + "The editor caret position is not properly set."); + } + + function setCaretPosition(aLine) { + gEditor.setCursor({ line: aLine - 1, ch: 0 }); + } + + function clickOnBreakpoint(aIndex) { + EventUtils.sendMouseEvent({ type: "click" }, + gDebugger.document.querySelectorAll(".dbg-breakpoint")[aIndex], + gDebugger); + } + + function waitForConditionUpdate() { + // This will close the popup and send another request to update + // the condition + gSources._hideConditionalPopup(); + return waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION); + } + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretAndScopes(gPanel, 17); + callInTab(gTab, "ermahgerd"); + yield onCaretUpdated; + + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(queries.getSourceCount(getState()), 1, + "Found the expected number of sources."); + is(gEditor.getText().indexOf("ermahgerd"), 253, + "The correct source was loaded initially."); + is(gSources.selectedValue, gSources.values[0], + "The correct source is selected."); + + is(queries.getBreakpoints(getState()).length, 0, + "No breakpoints currently added."); + + yield addBreakpoint1(); + testBreakpoint(18, false, undefined); + + yield addBreakpoint2(); + testBreakpoint(19, false, undefined); + yield modBreakpoint2(); + testBreakpoint(19, true, undefined); + yield waitForConditionUpdate(); + yield addBreakpoint3(); + testBreakpoint(20, true, ""); + yield waitForConditionUpdate(); + yield modBreakpoint3(); + testBreakpoint(20, false, "bamboocha"); + yield addBreakpoint4(); + testBreakpoint(21, false, undefined); + yield delBreakpoint4(); + + setCaretPosition(18); + is(gSources._selectedBreakpoint.location.line, 18, + "The selected breakpoint is line 18"); + yield testBreakpoint(18, false, undefined); + + setCaretPosition(19); + is(gSources._selectedBreakpoint.location.line, 19, + "The selected breakpoint is line 19"); + yield testBreakpoint(19, false, ""); + + setCaretPosition(20); + is(gSources._selectedBreakpoint.location.line, 20, + "The selected breakpoint is line 20"); + yield testBreakpoint(20, false, "bamboocha"); + + setCaretPosition(17); + yield testNoBreakpoint(17); + + setCaretPosition(21); + yield testNoBreakpoint(21); + + clickOnBreakpoint(0); + is(gSources._selectedBreakpoint.location.line, 18, + "The selected breakpoint is line 18"); + yield testBreakpoint(18, false, undefined); + + clickOnBreakpoint(1); + is(gSources._selectedBreakpoint.location.line, 19, + "The selected breakpoint is line 19"); + yield testBreakpoint(19, false, ""); + + clickOnBreakpoint(2); + is(gSources._selectedBreakpoint.location.line, 20, + "The selected breakpoint is line 20"); + testBreakpoint(20, true, "bamboocha"); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-03.js new file mode 100644 index 000000000..b83c96e39 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-03.js @@ -0,0 +1,73 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that conditional breakpoints survive disabled breakpoints + * (with server-side support) + */ + +const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + function waitForConditionUpdate() { + // This will close the popup and send another request to update + // the condition + gSources._hideConditionalPopup(); + return waitForDispatch(gPanel, constants.SET_BREAKPOINT_CONDITION); + } + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretAndScopes(gPanel, 17); + callInTab(gTab, "ermahgerd"); + yield onCaretUpdated; + + const location = { actor: gSources.selectedValue, line: 18 }; + + yield actions.addBreakpoint(location, "hello"); + yield actions.disableBreakpoint(location); + yield actions.addBreakpoint(location); + + const bp = queries.getBreakpoint(getState(), location); + is(bp.condition, "hello", "The conditional expression is correct."); + + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWN); + EventUtils.sendMouseEvent({ type: "click" }, + gDebugger.document.querySelector(".dbg-breakpoint"), + gDebugger); + yield finished; + + const textbox = gDebugger.document.getElementById("conditional-breakpoint-panel-textbox"); + is(textbox.value, "hello", "The expression is correct (2)."); + + yield waitForConditionUpdate(); + yield actions.disableBreakpoint(location); + yield actions.setBreakpointCondition(location, "foo"); + yield actions.addBreakpoint(location); + + finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.CONDITIONAL_BREAKPOINT_POPUP_SHOWN); + EventUtils.sendMouseEvent({ type: "click" }, + gDebugger.document.querySelector(".dbg-breakpoint"), + gDebugger); + yield finished; + is(textbox.value, "foo", "The expression is correct (3)."); + + yield resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-04.js new file mode 100644 index 000000000..2f35c4d60 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-04.js @@ -0,0 +1,46 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that conditional breakpoints with undefined expressions + * maintain their conditions when re-enabling them (with + * server-side support) + */ + +const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretAndScopes(gPanel, 17); + callInTab(gTab, "ermahgerd"); + yield onCaretUpdated; + + const location = { actor: gSources.selectedValue, line: 18 }; + + yield actions.addBreakpoint(location, ""); + yield actions.disableBreakpoint(location); + yield actions.addBreakpoint(location); + + const bp = queries.getBreakpoint(getState(), location); + is(bp.condition, "", "The conditional expression is correct."); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-05.js new file mode 100644 index 000000000..21607d8fd --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_server-conditional-bp-05.js @@ -0,0 +1,134 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test conditional breakpoints throwing exceptions + * with server support + */ + +const TAB_URL = EXAMPLE_URL + "doc_conditional-breakpoints.html"; + +function test() { + const options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const queries = gDebugger.require("./content/queries"); + const constants = gDebugger.require("./content/constants"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + function resumeAndTestBreakpoint(line) { + let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line)); + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + + return finished; + } + + function resumeAndTestNoBreakpoint() { + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_CLEARED).then(() => { + is(gSources.itemCount, 1, + "Found the expected number of sources."); + is(gEditor.getText().indexOf("ermahgerd"), 253, + "The correct source was loaded initially."); + is(gSources.selectedValue, gSources.values[0], + "The correct source is selected."); + + ok(gSources.selectedItem, + "There should be a selected source in the sources pane."); + ok(!gSources._selectedBreakpoint, + "There should be no selected breakpoint in the sources pane."); + is(gSources._conditionalPopupVisible, false, + "The breakpoint conditional expression popup should not be shown."); + + is(gDebugger.document.querySelectorAll(".dbg-stackframe").length, 0, + "There should be no visible stackframes."); + is(gDebugger.document.querySelectorAll(".dbg-breakpoint").length, 6, + "There should be thirteen visible breakpoints."); + }); + + gDebugger.gThreadClient.resume(); + + return finished; + } + + function testBreakpoint(line, highlightBreakpoint) { + // Highlight the breakpoint only if required. + if (highlightBreakpoint) { + let finished = waitForCaretUpdated(gPanel, line).then(() => testBreakpoint(line)); + gSources.highlightBreakpoint({ actor: gSources.selectedValue, line: line }); + return finished; + } + + let selectedActor = gSources.selectedValue; + let selectedBreakpoint = gSources._selectedBreakpoint; + let selectedBreakpointItem = gSources._getBreakpoint(selectedBreakpoint); + let source = queries.getSource(getState(), selectedActor); + + ok(selectedActor, + "There should be a selected item in the sources pane."); + ok(selectedBreakpoint, + "There should be a selected breakpoint."); + ok(selectedBreakpointItem, + "There should be a selected breakpoint item in the sources pane."); + + is(selectedBreakpoint.location.actor, source.actor, + "The breakpoint on line " + line + " wasn't added on the correct source."); + is(selectedBreakpoint.location.line, line, + "The breakpoint on line " + line + " wasn't found."); + is(!!selectedBreakpoint.location.disabled, false, + "The breakpoint on line " + line + " should be enabled."); + is(gSources._conditionalPopupVisible, false, + "The breakpoint conditional expression popup should not have been shown."); + + isnot(selectedBreakpoint.condition, undefined, + "The breakpoint on line " + line + " should have a conditional expression."); + + ok(isCaretPos(gPanel, line), + "The editor caret position is not properly set."); + } + + Task.spawn(function* () { + let onCaretUpdated = waitForCaretAndScopes(gPanel, 17); + callInTab(gTab, "ermahgerd"); + yield onCaretUpdated; + + yield actions.addBreakpoint( + { actor: gSources.selectedValue, line: 18 }, " 1a" + ); + yield actions.addBreakpoint( + { actor: gSources.selectedValue, line: 19 }, "new Error()" + ); + yield actions.addBreakpoint( + { actor: gSources.selectedValue, line: 20 }, "true" + ); + yield actions.addBreakpoint( + { actor: gSources.selectedValue, line: 21 }, "false" + ); + yield actions.addBreakpoint( + { actor: gSources.selectedValue, line: 22 }, "0" + ); + yield actions.addBreakpoint( + { actor: gSources.selectedValue, line: 23 }, "randomVar" + ); + + yield resumeAndTestBreakpoint(18); + yield resumeAndTestBreakpoint(19); + yield resumeAndTestBreakpoint(20); + yield resumeAndTestBreakpoint(23); + yield resumeAndTestNoBreakpoint(); + + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-01.js new file mode 100644 index 000000000..c0681fec6 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-01.js @@ -0,0 +1,170 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can set breakpoints and step through source mapped + * coffee script. + */ + +const TAB_URL = EXAMPLE_URL + "doc_binary_search.html"; +const COFFEE_URL = EXAMPLE_URL + "code_binary_search.coffee"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources; + +function test() { + let options = { + source: COFFEE_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + + checkSourceMapsEnabled(); + + checkInitialSource(); + testSetBreakpoint() + .then(testSetBreakpointBlankLine) + .then(testHitBreakpoint) + .then(testStepping) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function checkSourceMapsEnabled() { + is(Services.prefs.getBoolPref("devtools.debugger.source-maps-enabled"), true, + "The source maps functionality should be enabled by default."); + is(gDebugger.Prefs.sourceMapsEnabled, true, + "The source maps pref should be true from startup."); + is(gDebugger.DebuggerView.Options._showOriginalSourceItem.getAttribute("checked"), "true", + "Source maps should be enabled from startup."); +} + +function checkInitialSource() { + isnot(gSources.selectedItem.attachment.source.url.indexOf(".coffee"), -1, + "The debugger should show the source mapped coffee source file."); + is(gSources.selectedValue.indexOf(".js"), -1, + "The debugger should not show the generated js source file."); + is(gEditor.getText().indexOf("isnt"), 218, + "The debugger's editor should have the coffee source source displayed."); + is(gEditor.getText().indexOf("function"), -1, + "The debugger's editor should not have the JS source displayed."); +} + +function testSetBreakpoint() { + let deferred = promise.defer(); + let sourceForm = getSourceForm(gSources, COFFEE_URL); + + gDebugger.gThreadClient.interrupt(aResponse => { + let source = gDebugger.gThreadClient.source(sourceForm); + source.setBreakpoint({ line: 5 }, aResponse => { + ok(!aResponse.error, + "Should be able to set a breakpoint in a coffee source file."); + ok(!aResponse.actualLocation, + "Should be able to set a breakpoint on line 5."); + + deferred.resolve(); + }); + }); + + return deferred.promise; +} + +function testSetBreakpointBlankLine() { + let deferred = promise.defer(); + let sourceForm = getSourceForm(gSources, COFFEE_URL); + + let source = gDebugger.gThreadClient.source(sourceForm); + source.setBreakpoint({ line: 8 }, aResponse => { + ok(!aResponse.error, + "Should be able to set a breakpoint in a coffee source file on a blank line."); + ok(!aResponse.isPending, + "Should not be a pending breakpoint."); + ok(!aResponse.actualLocation, + "Should not be a moved breakpoint."); + + deferred.resolve(); + }); + + return deferred.promise; +} + +function testHitBreakpoint() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.resume(aResponse => { + ok(!aResponse.error, "Shouldn't get an error resuming."); + is(aResponse.type, "resumed", "Type should be 'resumed'."); + + gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => { + is(aPacket.type, "paused", + "We should now be paused again."); + is(aPacket.why.type, "breakpoint", + "and the reason we should be paused is because we hit a breakpoint."); + + // Check that we stopped at the right place, by making sure that the + // environment is in the state that we expect. + is(aPacket.frame.environment.bindings.variables.start.value, 0, + "'start' is 0."); + is(aPacket.frame.environment.bindings.variables.stop.value.type, "undefined", + "'stop' hasn't been assigned to yet."); + is(aPacket.frame.environment.bindings.variables.pivot.value.type, "undefined", + "'pivot' hasn't been assigned to yet."); + + waitForCaretUpdated(gPanel, 5).then(deferred.resolve); + }); + + // This will cause the breakpoint to be hit, and put us back in the + // paused state. + callInTab(gTab, "binary_search", [0, 2, 3, 5, 7, 10], 5); + }); + + return deferred.promise; +} + +function testStepping() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.stepIn(aResponse => { + ok(!aResponse.error, "Shouldn't get an error resuming."); + is(aResponse.type, "resumed", "Type should be 'resumed'."); + + // After stepping, we will pause again, so listen for that. + gDebugger.gThreadClient.addOneTimeListener("paused", (aEvent, aPacket) => { + is(aPacket.type, "paused", + "We should now be paused again."); + is(aPacket.why.type, "resumeLimit", + "and the reason we should be paused is because we hit the resume limit."); + + // Check that we stopped at the right place, by making sure that the + // environment is in the state that we expect. + is(aPacket.frame.environment.bindings.variables.start.value, 0, + "'start' is 0."); + is(aPacket.frame.environment.bindings.variables.stop.value, 5, + "'stop' is 5."); + is(aPacket.frame.environment.bindings.variables.pivot.value.type, "undefined", + "'pivot' hasn't been assigned to yet."); + + waitForCaretUpdated(gPanel, 6).then(deferred.resolve); + }); + }); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-02.js new file mode 100644 index 000000000..e406c9ce4 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-02.js @@ -0,0 +1,153 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can toggle between the original and generated sources. + */ + +const TAB_URL = EXAMPLE_URL + "doc_binary_search.html"; +const JS_URL = EXAMPLE_URL + "code_binary_search.js"; + +var gTab, gPanel, gDebugger, gEditor; +var gSources, gFrames, gPrefs, gOptions; + +function test() { + let options = { + source: EXAMPLE_URL + "code_binary_search.coffee", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gFrames = gDebugger.DebuggerView.StackFrames; + gPrefs = gDebugger.Prefs; + gOptions = gDebugger.DebuggerView.Options; + + testToggleGeneratedSource() + .then(testSetBreakpoint) + .then(testToggleOnPause) + .then(testResume) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testToggleGeneratedSource() { + let finished = waitForSourceShown(gPanel, ".js").then(() => { + is(gPrefs.sourceMapsEnabled, false, + "The source maps pref should have been set to false."); + is(gOptions._showOriginalSourceItem.getAttribute("checked"), "false", + "Source maps should now be disabled."); + + is(gSources.selectedItem.attachment.source.url.indexOf(".coffee"), -1, + "The debugger should not show the source mapped coffee source file."); + isnot(gSources.selectedItem.attachment.source.url.indexOf(".js"), -1, + "The debugger should show the generated js source file."); + + is(gEditor.getText().indexOf("isnt"), -1, + "The debugger's editor should not have the coffee source source displayed."); + is(gEditor.getText().indexOf("function"), 36, + "The debugger's editor should have the JS source displayed."); + }); + + gOptions._showOriginalSourceItem.setAttribute("checked", "false"); + gOptions._toggleShowOriginalSource(); + gOptions._onPopupHidden(); + + return finished; +} + +function testSetBreakpoint() { + let deferred = promise.defer(); + let sourceForm = getSourceForm(gSources, JS_URL); + let source = gDebugger.gThreadClient.source(sourceForm); + + source.setBreakpoint({ line: 7 }, aResponse => { + ok(!aResponse.error, + "Should be able to set a breakpoint in a js file."); + + gDebugger.gClient.addOneTimeListener("resumed", () => { + waitForCaretAndScopes(gPanel, 7).then(() => { + // Make sure that we have JavaScript stack frames. + is(gFrames.itemCount, 1, + "Should have only one frame."); + is(gFrames.getItemAtIndex(0).attachment.url.indexOf(".coffee"), -1, + "First frame should not be a coffee source frame."); + isnot(gFrames.getItemAtIndex(0).attachment.url.indexOf(".js"), -1, + "First frame should be a JS frame."); + + deferred.resolve(); + }); + + // This will cause the breakpoint to be hit, and put us back in the + // paused state. + callInTab(gTab, "binary_search", [0, 2, 3, 5, 7, 10], 5); + }); + }); + + return deferred.promise; +} + +function testToggleOnPause() { + let finished = waitForSourceAndCaretAndScopes(gPanel, ".coffee", 5).then(() => { + is(gPrefs.sourceMapsEnabled, true, + "The source maps pref should have been set to true."); + is(gOptions._showOriginalSourceItem.getAttribute("checked"), "true", + "Source maps should now be enabled."); + + isnot(gSources.selectedItem.attachment.source.url.indexOf(".coffee"), -1, + "The debugger should show the source mapped coffee source file."); + is(gSources.selectedItem.attachment.source.url.indexOf(".js"), -1, + "The debugger should not show the generated js source file."); + + is(gEditor.getText().indexOf("isnt"), 218, + "The debugger's editor should have the coffee source source displayed."); + is(gEditor.getText().indexOf("function"), -1, + "The debugger's editor should not have the JS source displayed."); + + // Make sure that we have coffee source stack frames. + is(gFrames.itemCount, 1, + "Should have only one frame."); + is(gFrames.getItemAtIndex(0).attachment.url.indexOf(".js"), -1, + "First frame should not be a JS frame."); + isnot(gFrames.getItemAtIndex(0).attachment.url.indexOf(".coffee"), -1, + "First frame should be a coffee source frame."); + }); + + gOptions._showOriginalSourceItem.setAttribute("checked", "true"); + gOptions._toggleShowOriginalSource(); + gOptions._onPopupHidden(); + + return finished; +} + +function testResume() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.resume(aResponse => { + ok(!aResponse.error, "Shouldn't get an error resuming."); + is(aResponse.type, "resumed", "Type should be 'resumed'."); + + deferred.resolve(); + }); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; + gFrames = null; + gPrefs = null; + gOptions = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-03.js new file mode 100644 index 000000000..b729be49f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-03.js @@ -0,0 +1,88 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that we can debug minified javascript with source maps. + */ + +const TAB_URL = EXAMPLE_URL + "doc_minified.html"; +const JS_URL = EXAMPLE_URL + "code_math.js"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources, gFrames; + +function test() { + let options = { + source: JS_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gFrames = gDebugger.DebuggerView.StackFrames; + + checkInitialSource() + testSetBreakpoint() + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function checkInitialSource() { + isnot(gSources.selectedItem.attachment.source.url.indexOf(".js"), -1, + "The debugger should not show the minified js file."); + is(gSources.selectedItem.attachment.source.url.indexOf(".min.js"), -1, + "The debugger should show the original js file."); + is(gEditor.getText().split("\n").length, 46, + "The debugger's editor should have the original source displayed, " + + "not the whitespace stripped minified version."); +} + +function testSetBreakpoint() { + let deferred = promise.defer(); + let sourceForm = getSourceForm(gSources, JS_URL); + let source = gDebugger.gThreadClient.source(sourceForm); + + source.setBreakpoint({ line: 30 }, aResponse => { + ok(!aResponse.error, + "Should be able to set a breakpoint in a js file."); + ok(!aResponse.actualLocation, + "Should be able to set a breakpoint on line 30."); + + gDebugger.gClient.addOneTimeListener("resumed", () => { + waitForCaretAndScopes(gPanel, 30).then(() => { + // Make sure that we have the right stack frames. + is(gFrames.itemCount, 9, + "Should have nine frames."); + is(gFrames.getItemAtIndex(0).attachment.url.indexOf(".min.js"), -1, + "First frame should not be a minified JS frame."); + isnot(gFrames.getItemAtIndex(0).attachment.url.indexOf(".js"), -1, + "First frame should be a JS frame."); + + deferred.resolve(); + }); + + // This will cause the breakpoint to be hit, and put us back in the + // paused state. + callInTab(gTab, "arithmetic"); + }); + }); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; + gFrames = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-04.js new file mode 100644 index 000000000..f3c4e89a8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-04.js @@ -0,0 +1,187 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that bogus source maps don't break debugging. + */ + +const TAB_URL = EXAMPLE_URL + "doc_minified_bogus_map.html"; +const JS_URL = EXAMPLE_URL + "code_math_bogus_map.js"; + +// This test causes an error to be logged in the console, which appears in TBPL +// logs, so we are disabling that here. +DevToolsUtils.reportingDisabled = true; + +var gPanel, gDebugger, gFrames, gSources, gPrefs, gOptions; + +function test() { + let options = { + source: JS_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gFrames = gDebugger.DebuggerView.StackFrames; + gSources = gDebugger.DebuggerView.Sources; + gPrefs = gDebugger.Prefs; + gOptions = gDebugger.DebuggerView.Options; + + is(gPrefs.pauseOnExceptions, false, + "The pause-on-exceptions pref should be disabled by default."); + isnot(gOptions._pauseOnExceptionsItem.getAttribute("checked"), "true", + "The pause-on-exceptions menu item should not be checked."); + + checkInitialSource(); + enablePauseOnExceptions() + .then(disableIgnoreCaughtExceptions) + .then(testSetBreakpoint) + .then(reloadPage) + .then(testHitBreakpoint) + .then(enableIgnoreCaughtExceptions) + .then(disablePauseOnExceptions) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function checkInitialSource() { + isnot(gSources.selectedItem.attachment.source.url.indexOf("code_math_bogus_map.js"), -1, + "The debugger should show the minified js file."); +} + +function enablePauseOnExceptions() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + is(gPrefs.pauseOnExceptions, true, + "The pause-on-exceptions pref should now be enabled."); + + ok(true, "Pausing on exceptions was enabled."); + deferred.resolve(); + }); + + gOptions._pauseOnExceptionsItem.setAttribute("checked", "true"); + gOptions._togglePauseOnExceptions(); + + return deferred.promise; +} + +function disableIgnoreCaughtExceptions() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + is(gPrefs.ignoreCaughtExceptions, false, + "The ignore-caught-exceptions pref should now be disabled."); + + ok(true, "Ignore caught exceptions was disabled."); + deferred.resolve(); + }); + + gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "false"); + gOptions._toggleIgnoreCaughtExceptions(); + + return deferred.promise; +} + +function testSetBreakpoint() { + let deferred = promise.defer(); + let sourceForm = getSourceForm(gSources, JS_URL); + let source = gDebugger.gThreadClient.source(sourceForm); + + source.setBreakpoint({ line: 3, column: 18 }, aResponse => { + ok(!aResponse.error, + "Should be able to set a breakpoint in a js file."); + ok(!aResponse.actualLocation, + "Should be able to set a breakpoint on line 3 and column 18."); + + deferred.resolve(); + }); + + return deferred.promise; +} + +function reloadPage() { + let loaded = waitForSourceAndCaret(gPanel, ".js", 3); + gDebugger.DebuggerController._target.activeTab.reload(); + return loaded.then(() => ok(true, "Page was reloaded and execution resumed.")); +} + +function testHitBreakpoint() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.resume(aResponse => { + ok(!aResponse.error, "Shouldn't get an error resuming."); + is(aResponse.type, "resumed", "Type should be 'resumed'."); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => { + is(gFrames.itemCount, 2, "Should have two frames."); + + // This is weird, but we need to let the debugger a chance to + // update first + executeSoon(() => { + gDebugger.gThreadClient.resume(() => { + gDebugger.gThreadClient.addOneTimeListener("paused", () => { + gDebugger.gThreadClient.resume(() => { + // We also need to make sure the next step doesn't add a + // "resumed" handler until this is completely finished + executeSoon(() => { + deferred.resolve(); + }); + }); + }); + }); + }); + }); + }); + + return deferred.promise; +} + +function enableIgnoreCaughtExceptions() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + is(gPrefs.ignoreCaughtExceptions, true, + "The ignore-caught-exceptions pref should now be enabled."); + + ok(true, "Ignore caught exceptions was enabled."); + deferred.resolve(); + }); + + gOptions._ignoreCaughtExceptionsItem.setAttribute("checked", "true"); + gOptions._toggleIgnoreCaughtExceptions(); + + return deferred.promise; +} + +function disablePauseOnExceptions() { + let deferred = promise.defer(); + + gDebugger.gThreadClient.addOneTimeListener("resumed", () => { + is(gPrefs.pauseOnExceptions, false, + "The pause-on-exceptions pref should now be disabled."); + + ok(true, "Pausing on exceptions was disabled."); + deferred.resolve(); + }); + + gOptions._pauseOnExceptionsItem.setAttribute("checked", "false"); + gOptions._togglePauseOnExceptions(); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gPanel = null; + gDebugger = null; + gFrames = null; + gSources = null; + gPrefs = null; + gOptions = null; + DevToolsUtils.reportingDisabled = false; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-bookmarklet.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-bookmarklet.js new file mode 100644 index 000000000..506e24006 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-bookmarklet.js @@ -0,0 +1,53 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure javascript bookmarklet scripts appear and load correctly in the source list + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-bookmarklet.html"; + +const BOOKMARKLET_SCRIPT_CODE = "console.log('bookmarklet executed');"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + const gBreakpoints = gDebugger.DebuggerController.Breakpoints; + const getState = gDebugger.DebuggerController.getState; + const constants = gDebugger.require("./content/constants"); + const queries = gDebugger.require("./content/queries"); + const actions = bindActionCreators(gPanel); + + return Task.spawn(function* () { + const added = waitForNextDispatch(gDebugger.DebuggerController, constants.ADD_SOURCE); + // NOTE: devtools debugger panel needs to be already open, + // or the bookmarklet script will not be shown in the sources panel + callInTab(gTab, "injectBookmarklet", BOOKMARKLET_SCRIPT_CODE); + yield added; + + is(queries.getSourceCount(getState()), 2, "Should have 2 sources"); + + const sources = queries.getSources(getState()); + const sourceActor = Object.keys(sources).filter(k => { + return sources[k].url.indexOf("javascript:") === 0; + })[0]; + const source = sources[sourceActor]; + ok(source, "Source exists."); + + let res = yield actions.loadSourceText(source); + is(res.text, BOOKMARKLET_SCRIPT_CODE, "source is correct"); + is(res.contentType, "text/javascript", "contentType is correct"); + + yield closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-cache.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-cache.js new file mode 100644 index 000000000..2c5d9d0e2 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-cache.js @@ -0,0 +1,147 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests if the sources cache knows how to cache sources when prompted. + */ + +const TAB_URL = EXAMPLE_URL + "doc_function-search.html"; +const TOTAL_SOURCES = 4; + +function test() { + let options = { + source: EXAMPLE_URL + "code_function-search-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => { + const gTab = aTab; + const gDebuggee = aDebuggee; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const gPrevLabelsCache = gDebugger.SourceUtils._labelsCache; + const gPrevGroupsCache = gDebugger.SourceUtils._groupsCache; + const getState = gDebugger.DebuggerController.getState; + const queries = gDebugger.require("./content/queries"); + const actions = bindActionCreators(gPanel); + + function initialChecks() { + ok(gEditor.getText().includes("First source!"), + "Editor text contents appears to be correct."); + is(gSources.selectedItem.attachment.label, "code_function-search-01.js", + "The currently selected label in the sources container is correct."); + ok(getSelectedSourceURL(gSources).includes("code_function-search-01.js"), + "The currently selected value in the sources container appears to be correct."); + + is(gSources.itemCount, TOTAL_SOURCES, + "There should be " + TOTAL_SOURCES + " sources present in the sources list."); + is(gSources.visibleItems.length, TOTAL_SOURCES, + "There should be " + TOTAL_SOURCES + " sources visible in the sources list."); + is(gSources.attachments.length, TOTAL_SOURCES, + "There should be " + TOTAL_SOURCES + " attachments stored in the sources container model."); + is(gSources.values.length, TOTAL_SOURCES, + "There should be " + TOTAL_SOURCES + " values stored in the sources container model."); + + info("Source labels: " + gSources.attachments.toSource()); + info("Source values: " + gSources.values.toSource()); + + is(gSources.attachments[0].label, "code_function-search-01.js", + "The first source label is correct."); + ok(gSources.attachments[0].source.url.includes("code_function-search-01.js"), + "The first source value appears to be correct."); + + is(gSources.attachments[1].label, "code_function-search-02.js", + "The second source label is correct."); + ok(gSources.attachments[1].source.url.includes("code_function-search-02.js"), + "The second source value appears to be correct."); + + is(gSources.attachments[2].label, "code_function-search-03.js", + "The third source label is correct."); + ok(gSources.attachments[2].source.url.includes("code_function-search-03.js"), + "The third source value appears to be correct."); + + is(gSources.attachments[3].label, "doc_function-search.html", + "The third source label is correct."); + ok(gSources.attachments[3].source.url.includes("doc_function-search.html"), + "The third source value appears to be correct."); + + is(gDebugger.SourceUtils._labelsCache.size, TOTAL_SOURCES, + "There should be " + TOTAL_SOURCES + " labels cached."); + is(gDebugger.SourceUtils._groupsCache.size, TOTAL_SOURCES, + "There should be " + TOTAL_SOURCES + " groups cached."); + } + + function performReloadAndTestState() { + gDebugger.gTarget.once("will-navigate", testStateBeforeReload); + gDebugger.gTarget.once("navigate", testStateAfterReload); + return reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN); + } + + function testCacheIntegrity(cachedSources) { + const contents = { + [EXAMPLE_URL + "code_function-search-01.js"]: "First source!", + [EXAMPLE_URL + "code_function-search-02.js"]: "Second source!", + [EXAMPLE_URL + "code_function-search-03.js"]: "Third source!", + [EXAMPLE_URL + "doc_function-search.html"]: "Peanut butter jelly time!" + }; + + const sourcesText = getState().sources.sourcesText; + is(Object.keys(sourcesText).length, cachedSources.length, + "The right number of sources is cached"); + + cachedSources.forEach(sourceUrl => { + const source = queries.getSourceByURL(getState(), EXAMPLE_URL + sourceUrl); + const content = queries.getSourceText(getState(), source.actor); + ok(content, "Source text is cached"); + ok(content.text.includes(contents[source.url]), "Source text is correct"); + }); + } + + function fetchAllSources() { + const sources = queries.getSources(getState()); + return Promise.all(Object.keys(sources).map(k => { + const source = sources[k]; + return actions.loadSourceText(source); + })); + } + + function testStateBeforeReload() { + is(gSources.itemCount, 0, + "There should be no sources present in the sources list during reload."); + is(gDebugger.SourceUtils._labelsCache, gPrevLabelsCache, + "The labels cache has been refreshed during reload and no new objects were created."); + is(gDebugger.SourceUtils._groupsCache, gPrevGroupsCache, + "The groups cache has been refreshed during reload and no new objects were created."); + is(gDebugger.SourceUtils._labelsCache.size, 0, + "There should be no labels cached during reload"); + is(gDebugger.SourceUtils._groupsCache.size, 0, + "There should be no groups cached during reload"); + } + + function testStateAfterReload() { + is(gSources.itemCount, TOTAL_SOURCES, + "There should be " + TOTAL_SOURCES + " sources present in the sources list."); + is(gDebugger.SourceUtils._labelsCache.size, TOTAL_SOURCES, + "There should be " + TOTAL_SOURCES + " labels cached after reload."); + is(gDebugger.SourceUtils._groupsCache.size, TOTAL_SOURCES, + "There should be " + TOTAL_SOURCES + " groups cached after reload."); + } + + Task.spawn(function* () { + yield initialChecks(); + yield testCacheIntegrity(["code_function-search-01.js"]); + yield fetchAllSources(); + yield testCacheIntegrity([ + "code_function-search-01.js", + "code_function-search-02.js", + "code_function-search-03.js", + "doc_function-search.html" + ]); + yield performReloadAndTestState(); + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-01.js new file mode 100644 index 000000000..967c98cff --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-01.js @@ -0,0 +1,57 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests the "Copy URL" functionality of the sources panel context menu + */ + +const TAB_URL = EXAMPLE_URL + "doc_function-search.html"; +const SCRIPT_URI = EXAMPLE_URL + "code_function-search-01.js"; + +function test() { + let gTab, gPanel, gDebugger, gSources; + + let options = { + source: SCRIPT_URI, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + + openContextMenu() + .then(testCopyMenuItem) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); + + function clickCopyURL() { + return new Promise((resolve, reject) => { + let copyURLMenuItem = gDebugger.document.getElementById("debugger-sources-context-copyurl"); + if (!copyURLMenuItem) { + reject(new Error("The Copy URL context menu item is not available.")); + } + + ok(copyURLMenuItem, "The Copy URL context menu item is available."); + EventUtils.synthesizeMouseAtCenter(copyURLMenuItem, {}, gDebugger); + resolve(); + }); + } + + function testCopyMenuItem() { + return waitForClipboardPromise(clickCopyURL, SCRIPT_URI); + } + + function openContextMenu() { + let contextMenu = gDebugger.document.getElementById("debuggerSourcesContextMenu"); + let contextMenuShown = once(contextMenu, "popupshown"); + EventUtils.synthesizeMouseAtCenter(gSources.selectedItem.prebuiltNode, {type: "contextmenu"}, gDebugger); + return contextMenuShown; + } +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-02.js new file mode 100644 index 000000000..da6668f51 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-02.js @@ -0,0 +1,75 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests the "Open in New Tab" functionality of the sources panel context menu + */ + +const TAB_URL = EXAMPLE_URL + "doc_function-search.html"; +const SCRIPT_URI = EXAMPLE_URL + "code_function-search-01.js"; + +function test() { + let gTab, gPanel, gDebugger; + let gSources; + + let options = { + source: SCRIPT_URI, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + + openContextMenu() + .then(testNewTabMenuItem) + .then(testNewTabURI) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); + + function testNewTabURI(tabUri) { + is(tabUri, SCRIPT_URI, "The tab contains the right script."); + gBrowser.removeCurrentTab(); + } + + function waitForTabOpen() { + return new Promise(resolve => { + gBrowser.tabContainer.addEventListener("TabOpen", function onOpen(e) { + gBrowser.tabContainer.removeEventListener("TabOpen", onOpen, false); + ok(true, "A new tab loaded"); + + gBrowser.addEventListener("DOMContentLoaded", function onTabLoad(e) { + gBrowser.removeEventListener("DOMContentLoaded", onTabLoad, false); + // Pass along the new tab's URI. + resolve(gBrowser.currentURI.spec); + }, false); + }, false); + }); + } + + function testNewTabMenuItem() { + return new Promise((resolve, reject) => { + let newTabMenuItem = gDebugger.document.getElementById("debugger-sources-context-newtab"); + if (!newTabMenuItem) { + reject(new Error("The Open in New Tab context menu item is not available.")); + } + + ok(newTabMenuItem, "The Open in New Tab context menu item is available."); + waitForTabOpen().then(resolve); + EventUtils.synthesizeMouseAtCenter(newTabMenuItem, {}, gDebugger); + }); + } + + function openContextMenu() { + let contextMenu = gDebugger.document.getElementById("debuggerSourcesContextMenu"); + let contextMenuShown = once(contextMenu, "popupshown"); + EventUtils.synthesizeMouseAtCenter(gSources.selectedItem.prebuiltNode, {type: "contextmenu"}, gDebugger); + return contextMenuShown; + } +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-01.js new file mode 100644 index 000000000..0e794d06c --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-01.js @@ -0,0 +1,44 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure eval scripts appear in the source list + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-eval.html"; + +function test() { + let gTab, gPanel, gDebugger; + let gSources, gBreakpoints; + + let options = { + source: EXAMPLE_URL + "code_script-eval.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + gBreakpoints = gDebugger.DebuggerController.Breakpoints; + + return Task.spawn(function* () { + is(gSources.values.length, 1, "Should have 1 source"); + + let newSource = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.NEW_SOURCE); + callInTab(gTab, "evalSource"); + yield newSource; + + is(gSources.values.length, 2, "Should have 2 sources"); + + let item = gSources.getItemForAttachment(e => e.label.indexOf("> eval") !== -1); + ok(item, "Source label is incorrect."); + is(item.attachment.group, gDebugger.L10N.getStr("evalGroupLabel"), + "Source group is incorrect"); + + yield closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-02.js new file mode 100644 index 000000000..b932df143 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-eval-02.js @@ -0,0 +1,55 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure eval scripts with the sourceURL pragma are correctly + * displayed + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-eval.html"; + +function test() { + let gTab, gPanel, gDebugger; + let gSources, gBreakpoints, gEditor; + + let options = { + source: EXAMPLE_URL + "code_script-eval.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + gBreakpoints = gDebugger.DebuggerController.Breakpoints; + gEditor = gDebugger.DebuggerView.editor; + const constants = gDebugger.require("./content/constants"); + const queries = gDebugger.require("./content/queries"); + const actions = bindActionCreators(gPanel); + const getState = gDebugger.DebuggerController.getState; + + return Task.spawn(function* () { + is(queries.getSourceCount(getState()), 1, "Should have 1 source"); + + const newSource = waitForDispatch(gPanel, constants.ADD_SOURCE); + callInTab(gTab, "evalSourceWithSourceURL"); + yield newSource; + + is(queries.getSourceCount(getState()), 2, "Should have 2 sources"); + + const source = queries.getSourceByURL(getState(), EXAMPLE_URL + "bar.js"); + ok(source, "Source exists."); + + let shown = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN); + actions.selectSource(source); + yield shown; + + ok(gEditor.getText().indexOf("bar = function() {") === 0, + "Correct source is shown"); + + yield closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-iframe-reload.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-iframe-reload.js new file mode 100644 index 000000000..63c53fba5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-iframe-reload.js @@ -0,0 +1,35 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure iframe scripts don't disappear after few reloads (bug 1259743) + */ + +"use strict"; + +const IFRAME_URL = "data:text/html;charset=utf-8," + + "<script>function fn() { console.log('hello'); }</script>" + + "<div onclick='fn()'>hello</div>"; +const TAB_URL = `data:text/html;charset=utf-8,<iframe src="${IFRAME_URL}"/>`; + +add_task(function* () { + let [,, panel] = yield initDebugger(); + let dbg = panel.panelWin; + let newSource; + + newSource = waitForDebuggerEvents(panel, dbg.EVENTS.NEW_SOURCE); + reload(panel, TAB_URL); + yield newSource; + ok(true, "Source event fired on initial load"); + + for (let i = 0; i < 5; i++) { + newSource = waitForDebuggerEvents(panel, dbg.EVENTS.NEW_SOURCE); + reload(panel); + yield newSource; + ok(true, `Source event fired after ${i + 1} reloads`); + } + + yield closeDebuggerAndFinish(panel); +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-keybindings.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-keybindings.js new file mode 100644 index 000000000..80f043637 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-keybindings.js @@ -0,0 +1,40 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests related to source panel keyboard shortcut bindings + */ + +const TAB_URL = EXAMPLE_URL + "doc_function-search.html"; +const SCRIPT_URI = EXAMPLE_URL + "code_function-search-01.js"; + +function test() { + let gTab, gPanel, gDebugger, gSources; + + let options = { + source: SCRIPT_URI, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + + testCopyURLShortcut() + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); + + function testCopyURLShortcut() { + return waitForClipboardPromise(sendCopyShortcut, SCRIPT_URI); + } + + function sendCopyShortcut() { + EventUtils.synthesizeKey("C", { accelKey: true }, gDebugger); + } +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-labels.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-labels.js new file mode 100644 index 000000000..1c4cfd6da --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-labels.js @@ -0,0 +1,172 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that urls are correctly shortened to unique labels. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; +const { ELLIPSIS } = require("devtools/shared/l10n"); + +function test() { + let gTab, gPanel, gDebugger; + let gSources, gUtils; + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(Task.async(function* ([aTab,, aPanel]) { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + gUtils = gDebugger.SourceUtils; + + let nananana = new Array(20).join(NaN); + + // Test trimming url queries. + + let someUrl = "a/b/c.d?test=1&random=4#reference"; + let shortenedUrl = "a/b/c.d"; + is(gUtils.trimUrlQuery(someUrl), shortenedUrl, + "Trimming the url query isn't done properly."); + + // Test trimming long urls with an ellipsis. + + let largeLabel = new Array(100).join("Beer can in Jamaican sounds like Bacon!"); + let trimmedLargeLabel = gUtils.trimUrlLength(largeLabel, 1234); + is(trimmedLargeLabel.length, 1235, + "Trimming large labels isn't done properly."); + ok(trimmedLargeLabel.endsWith(ELLIPSIS), + "Trimming large labels should add an ellipsis at the end : " + ELLIPSIS); + + // Test the sources list behaviour with certain urls. + + let urls = [ + { href: "http://some.address.com/random/", leaf: "subrandom/" }, + { href: "http://some.address.com/random/", leaf: "suprandom/?a=1" }, + { href: "http://some.address.com/random/", leaf: "?a=1" }, + { href: "https://another.address.org/random/subrandom/", leaf: "page.html" }, + + { href: "ftp://interesting.address.org/random/", leaf: "script.js" }, + { href: "ftp://interesting.address.com/random/", leaf: "script.js" }, + { href: "ftp://interesting.address.com/random/", leaf: "x/script.js" }, + { href: "ftp://interesting.address.com/random/", leaf: "x/y/script.js?a=1" }, + { href: "ftp://interesting.address.com/random/x/", leaf: "y/script.js?a=1&b=2" }, + { href: "ftp://interesting.address.com/random/x/y/", leaf: "script.js?a=1&b=2&c=3" }, + { href: "ftp://interesting.address.com/random/", leaf: "x/y/script.js?a=2" }, + { href: "ftp://interesting.address.com/random/x/", leaf: "y/script.js?a=2&b=3" }, + { href: "ftp://interesting.address.com/random/x/y/", leaf: "script.js?a=2&b=3&c=4" }, + + { href: "file://random/", leaf: "script_t1.js&a=1&b=2&c=3" }, + { href: "file://random/", leaf: "script_t2_1.js#id" }, + { href: "file://random/", leaf: "script_t2_2.js?a" }, + { href: "file://random/", leaf: "script_t2_3.js&b" }, + { href: "resource://random/", leaf: "script_t3_1.js#id?a=1&b=2" }, + { href: "resource://random/", leaf: "script_t3_2.js?a=1&b=2#id" }, + { href: "resource://random/", leaf: "script_t3_3.js&a=1&b=2#id" }, + + { href: nananana, leaf: "Batman!" + "{trim me, now and forevermore}" } + ]; + + is(gSources.itemCount, 1, + "Should contain the original source label in the sources widget."); + is(gSources.selectedIndex, 0, + "The first item in the sources widget should be selected (1)."); + is(gSources.selectedItem.attachment.label, "doc_recursion-stack.html", + "The first item in the sources widget should be selected (2)."); + is(getSelectedSourceURL(gSources), TAB_URL, + "The first item in the sources widget should be selected (3)."); + + let id = 0; + for (let { href, leaf } of urls) { + let url = href + leaf; + let actor = "actor" + id++; + let label = gUtils.trimUrlLength(gUtils.getSourceLabel(url)); + let group = gUtils.getSourceGroup(url); + let dummy = document.createElement("label"); + dummy.setAttribute("value", label); + + gSources.push([dummy, actor], { + attachment: { + source: { actor: actor, url: url }, + label: label, + group: group + } + }); + } + + info("Source locations:"); + info(gSources.values.toSource()); + + info("Source attachments:"); + info(gSources.attachments.toSource()); + + for (let { href, leaf, dupe } of urls) { + let url = href + leaf; + if (dupe) { + ok(!gSources.containsValue(getSourceActor(gSources, url)), "Shouldn't contain source: " + url); + } else { + ok(gSources.containsValue(getSourceActor(gSources, url)), "Should contain source: " + url); + } + } + + ok(gSources.getItemForAttachment(e => e.label == "random/subrandom/"), + "Source (0) label is incorrect."); + ok(gSources.getItemForAttachment(e => e.label == "random/suprandom/?a=1"), + "Source (1) label is incorrect."); + ok(gSources.getItemForAttachment(e => e.label == "random/?a=1"), + "Source (2) label is incorrect."); + ok(gSources.getItemForAttachment(e => e.label == "page.html"), + "Source (3) label is incorrect."); + + ok(gSources.getItemForAttachment(e => e.label == "script.js"), + "Source (4) label is incorrect."); + ok(gSources.getItemForAttachment(e => e.label == "random/script.js"), + "Source (5) label is incorrect."); + ok(gSources.getItemForAttachment(e => e.label == "random/x/script.js"), + "Source (6) label is incorrect."); + ok(gSources.getItemForAttachment(e => e.label == "script.js?a=1"), + "Source (7) label is incorrect."); + + ok(gSources.getItemForAttachment(e => e.label == "script_t1.js"), + "Source (8) label is incorrect."); + ok(gSources.getItemForAttachment(e => e.label == "script_t2_1.js"), + "Source (9) label is incorrect."); + ok(gSources.getItemForAttachment(e => e.label == "script_t2_2.js"), + "Source (10) label is incorrect."); + ok(gSources.getItemForAttachment(e => e.label == "script_t2_3.js"), + "Source (11) label is incorrect."); + ok(gSources.getItemForAttachment(e => e.label == "script_t3_1.js"), + "Source (12) label is incorrect."); + ok(gSources.getItemForAttachment(e => e.label == "script_t3_2.js"), + "Source (13) label is incorrect."); + ok(gSources.getItemForAttachment(e => e.label == "script_t3_3.js"), + "Source (14) label is incorrect."); + + ok(gSources.getItemForAttachment(e => e.label == nananana + "Batman!" + ELLIPSIS), + "Source (15) label is incorrect."); + + is(gSources.itemCount, urls.filter(({ dupe }) => !dupe).length + 1, + "Didn't get the correct number of sources in the list."); + + is(gSources.getItemByValue(getSourceActor(gSources, "http://some.address.com/random/subrandom/")).attachment.label, + "random/subrandom/", + "gSources.getItemByValue isn't functioning properly (0)."); + is(gSources.getItemByValue(getSourceActor(gSources, "http://some.address.com/random/suprandom/?a=1")).attachment.label, + "random/suprandom/?a=1", + "gSources.getItemByValue isn't functioning properly (1)."); + + is(gSources.getItemForAttachment(e => e.label == "random/subrandom/").attachment.source.url, + "http://some.address.com/random/subrandom/", + "gSources.getItemForAttachment isn't functioning properly (0)."); + is(gSources.getItemForAttachment(e => e.label == "random/suprandom/?a=1").attachment.source.url, + "http://some.address.com/random/suprandom/?a=1", + "gSources.getItemForAttachment isn't functioning properly (1)."); + + closeDebuggerAndFinish(gPanel); + })); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-large.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-large.js new file mode 100644 index 000000000..31b64c2fd --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-large.js @@ -0,0 +1,80 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that large files are treated differently in the debugger: + * 1) No parsing to determine current symbol is attempted when + * starting a search + */ + +const TAB_URL = EXAMPLE_URL + "doc_function-search.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_function-search-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => { + const gTab = aTab; + const gDebuggee = aDebuggee; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const Filtering = gDebugger.DebuggerView.Filtering; + + // Setting max size so that code_function-search-01.js will be + // considered a large file on first load + gDebugger.DebuggerView.LARGE_FILE_SIZE = 1; + + function testLargeFile() { + ok(gEditor.getText().length > gDebugger.DebuggerView.LARGE_FILE_SIZE, + "First source is considered a large file."); + is(gEditor.getMode().name, "javascript", + "Editor is syntax highlighting."); + ok(gEditor.getText().includes("First source!"), + "Editor text contents appears to be correct."); + + // Press ctrl+f with the cursor in a token + gEditor.focus(); + gEditor.setCursor({ line: 3, ch: 10}); + synthesizeKeyFromKeyTag(gDebugger.document.getElementById("tokenSearchKey")); + is(Filtering._searchbox.value, "#", + "Search box is NOT prefilled with current token"); + } + + function testSmallFile() { + ok(gEditor.getText().length < gDebugger.DebuggerView.LARGE_FILE_SIZE, + "Second source is considered a small file."); + is(gEditor.getMode().name, "javascript", + "Editor is syntax highlighting."); + ok(gEditor.getText().includes("First source!"), + "Editor text contents appears to be correct."); + + // Press ctrl+f with the cursor in a token + gEditor.focus(); + gEditor.setCursor({ line: 3, ch: 10}); + synthesizeKeyFromKeyTag(gDebugger.document.getElementById("tokenSearchKey")); + is(Filtering._searchbox.value, "#test", + "Search box is prefilled with current token"); + } + + Task.spawn(function* () { + yield testLargeFile(); + + info("Making it appear as a small file and then reselecting 01.js"); + gDebugger.DebuggerView.LARGE_FILE_SIZE = 1000; + gSources.selectedIndex = 1; + yield waitForSourceShown(gPanel, "-02.js"); + gSources.selectedIndex = 0; + yield waitForSourceShown(gPanel, "-01.js"); + + yield testSmallFile(); + + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-sorting.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-sorting.js new file mode 100644 index 000000000..2a600893b --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-sorting.js @@ -0,0 +1,141 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that urls are correctly sorted when added to the sources widget. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +var gTab, gPanel, gDebugger; +var gSources, gUtils; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + gUtils = gDebugger.SourceUtils; + + addSourceAndCheckOrder(1); + addSourceAndCheckOrder(2); + addSourceAndCheckOrder(3); + closeDebuggerAndFinish(gPanel); + }); +} + +function addSourceAndCheckOrder(aMethod) { + gSources.empty(); + gSources.suppressSelectionEvents = true; + + let urls = [ + { href: "ici://some.address.com/random/", leaf: "subrandom/" }, + { href: "ni://another.address.org/random/subrandom/", leaf: "page.html" }, + { href: "san://interesting.address.gro/random/", leaf: "script.js" }, + { href: "si://interesting.address.moc/random/", leaf: "script.js" }, + { href: "si://interesting.address.moc/random/", leaf: "x/script.js" }, + { href: "si://interesting.address.moc/random/", leaf: "x/y/script.js?a=1" }, + { href: "si://interesting.address.moc/random/x/", leaf: "y/script.js?a=1&b=2" }, + { href: "si://interesting.address.moc/random/x/y/", leaf: "script.js?a=1&b=2&c=3" } + ]; + + urls.sort(function (a, b) { + return Math.random() - 0.5; + }); + + let id = 0; + + switch (aMethod) { + case 1: + for (let { href, leaf } of urls) { + let url = href + leaf; + let actor = "actor" + id++; + let label = gUtils.getSourceLabel(url); + let dummy = document.createElement("label"); + gSources.push([dummy, actor], { + staged: true, + attachment: { + label: label + } + }); + } + gSources.commit({ sorted: true }); + break; + + case 2: + for (let { href, leaf } of urls) { + let url = href + leaf; + let actor = "actor" + id++; + let label = gUtils.getSourceLabel(url); + let dummy = document.createElement("label"); + gSources.push([dummy, actor], { + staged: false, + attachment: { + label: label + } + }); + } + break; + + case 3: + let i = 0; + for (; i < urls.length / 2; i++) { + let { href, leaf } = urls[i]; + let url = href + leaf; + let actor = "actor" + id++; + let label = gUtils.getSourceLabel(url); + let dummy = document.createElement("label"); + gSources.push([dummy, actor], { + staged: true, + attachment: { + label: label + } + }); + } + gSources.commit({ sorted: true }); + + for (; i < urls.length; i++) { + let { href, leaf } = urls[i]; + let url = href + leaf; + let actor = "actor" + id++; + let label = gUtils.getSourceLabel(url); + let dummy = document.createElement("label"); + gSources.push([dummy, actor], { + staged: false, + attachment: { + label: label + } + }); + } + break; + } + + checkSourcesOrder(aMethod); +} + +function checkSourcesOrder(aMethod) { + let attachments = gSources.attachments; + + for (let i = 0; i < attachments.length - 1; i++) { + let first = attachments[i].label; + let second = attachments[i + 1].label; + ok(first < second, + "Using method " + aMethod + ", " + + "the sources weren't in the correct order: " + first + " vs. " + second); + } +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gSources = null; + gUtils = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_sources-webext-contentscript.js b/devtools/client/debugger/test/mochitest/browser_dbg_sources-webext-contentscript.js new file mode 100644 index 000000000..2fd8067f9 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-webext-contentscript.js @@ -0,0 +1,63 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure eval scripts appear in the source list + */ + +const ADDON_PATH = "addon-webext-contentscript.xpi"; +const TAB_URL = EXAMPLE_URL + "doc_script_webext_contentscript.html"; + +let {getExtensionUUID} = Cu.import("resource://gre/modules/Extension.jsm", {}); + +function test() { + let gPanel, gDebugger; + let gSources, gAddon; + + let cleanup = function* (e) { + if (gAddon) { + // Remove the addon, if any. + yield removeAddon(gAddon); + } + if (gPanel) { + // Close the debugger panel, if any. + yield closeDebuggerAndFinish(gPanel); + } else { + // If no debugger panel was opened, call finish directly. + finish(); + } + }; + + return Task.spawn(function* () { + gAddon = yield addTemporaryAddon(ADDON_PATH); + let uuid = getExtensionUUID(gAddon.id); + + let options = { + source: `moz-extension://${uuid}/webext-content-script.js`, + line: 1 + }; + [,, gPanel] = yield initDebugger(TAB_URL, options); + gDebugger = gPanel.panelWin; + gSources = gDebugger.DebuggerView.Sources; + + is(gSources.values.length, 2, "Should have 2 sources"); + + let item = gSources.getItemForAttachment(attachment => { + return attachment.source.url.includes("moz-extension"); + }); + + ok(item, "Got the expected WebExtensions ContentScript source"); + ok(item && item.attachment.source.url.includes(item.attachment.group), + "The source is in the expected source group"); + is(item && item.attachment.label, "webext-content-script.js", + "Got the expected filename in the label"); + + yield cleanup(); + }).catch((e) => { + ok(false, `Got an unexpected exception: ${e}`); + // Cleanup in case of failures in the above task. + return Task.spawn(cleanup); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_split-console-keypress.js b/devtools/client/debugger/test/mochitest/browser_dbg_split-console-keypress.js new file mode 100644 index 000000000..5bfe0a61e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_split-console-keypress.js @@ -0,0 +1,108 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * When the split console is focused and the debugger is open, + * debugger shortcut keys like F11 should work + */ +const TAB_URL = EXAMPLE_URL + "doc_step-many-statements.html"; + +function test() { + // This does the same assertions over a series of sub-tests, and it + // can timeout in linux e10s. No sense in breaking it up into multiple + // tests, so request extra time. + requestLongerTimeout(2); + + let gDebugger, gToolbox, gThreadClient, gTab, gPanel; + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab, debuggeeWin, aPanel]) => { + gPanel = aPanel; + gDebugger = aPanel.panelWin; + gToolbox = gDevTools.getToolbox(aPanel.target); + gTab = aTab; + gThreadClient = gDebugger.DebuggerController.activeThread; + testConsole(); + }); + let testConsole = Task.async(function* () { + // We need to open the split console (with an ESC keypress), + // then get the script into a paused state by pressing a button in the page, + // ensure focus is in the split console, + // synthesize a few keys - important ones we share listener for are + // "resumeKey", "stepOverKey", "stepInKey", "stepOutKey" + // then check that + // * The input cursor remains in the console's input box + // * The paused state is as expected + // * the debugger cursor is where we want it + let jsterm = yield getSplitConsole(gToolbox, gDebugger); + // The console is now open (if not make the test fail already) + ok(gToolbox.splitConsole, "Split console is shown."); + + // Information for sub-tests. When 'key' is synthesized 'keyRepeat' times, + // cursor should be at 'caretLine' of this test.. + let stepTests = [ + {key: "VK_F11", keyRepeat: 1, caretLine: 16}, + {key: "VK_F11", keyRepeat: 2, caretLine: 18}, + {key: "VK_F11", keyRepeat: 2, caretLine: 27}, + {key: "VK_F10", keyRepeat: 1, caretLine: 27}, + {key: "VK_F11", keyRepeat: 1, caretLine: 18}, + {key: "VK_F11", keyRepeat: 5, caretLine: 32}, + {key: "VK_F11", modifier:"Shift", keyRepeat: 1, caretLine: 29}, + {key: "VK_F11", modifier:"Shift", keyRepeat: 2, caretLine: 34}, + {key: "VK_F11", modifier:"Shift", keyRepeat: 2, caretLine: 34} + ]; + // Trigger script that stops at debugger statement + executeSoon(() => generateMouseClickInTab(gTab, + "content.document.getElementById('start')")); + yield waitForPause(gThreadClient); + + // Focus the console and add event listener to track whether it loses focus + // (Must happen after generateMouseClickInTab() call) + let consoleLostFocus = false; + jsterm.focus(); + jsterm.inputNode.addEventListener("blur", () => {consoleLostFocus = true;}); + + is(gThreadClient.paused, true, + "Should be paused at debugger statement."); + // As long as we have test work to do.. + for (let i = 0, thisTest; thisTest = stepTests[i]; i++) { + // First we send another key event if required by the test + while (thisTest.keyRepeat > 0) { + thisTest.keyRepeat --; + let keyMods = thisTest.modifier === "Shift" ? {shiftKey:true} : {}; + executeSoon(() => {EventUtils.synthesizeKey(thisTest.key, keyMods);}); + yield waitForPause(gThreadClient); + } + + // We've sent the required number of keys + // Here are the conditions we're interested in: paused state, + // cursor still in console (tested later), caret correct in editor + is(gThreadClient.paused, true, + "Should still be paused"); + // ok(isCaretPos(gPanel, thisTest.caretLine), + // "Test " + i + ": CaretPos at line " + thisTest.caretLine); + ok(isDebugPos(gPanel, thisTest.caretLine), + "Test " + i + ": DebugPos at line " + thisTest.caretLine); + } + // Did focus go missing while we were stepping? + is(consoleLostFocus, false, "Console input should not lose focus"); + // We're done with the tests in the stepTests array + // Last key we test is "resume" + executeSoon(() => EventUtils.synthesizeKey("VK_F8", {})); + + // We reset the variable tracking loss of focus to test the resume case + consoleLostFocus = false; + + gPanel.target.on("thread-resumed", () => { + is(gThreadClient.paused, false, + "Should not be paused after resume"); + // Final test: did we preserve console inputNode focus during resume? + is(consoleLostFocus, false, "Resume - console should keep focus"); + closeDebuggerAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_split-console-paused-reload.js b/devtools/client/debugger/test/mochitest/browser_dbg_split-console-paused-reload.js new file mode 100644 index 000000000..e9daaa4bc --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_split-console-paused-reload.js @@ -0,0 +1,67 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Hitting ESC to open the split console when paused on reload should not stop + * the pending navigation. + */ + +function test() { + Task.spawn(runTests); +} + +function* runTests() { + let TAB_URL = EXAMPLE_URL + "doc_split-console-paused-reload.html"; + let options = { + source: TAB_URL, + line: 1 + }; + let [,, panel] = yield initDebugger(TAB_URL, options); + let dbgWin = panel.panelWin; + let sources = dbgWin.DebuggerView.Sources; + let frames = dbgWin.DebuggerView.StackFrames; + let toolbox = gDevTools.getToolbox(panel.target); + + yield panel.addBreakpoint({ actor: getSourceActor(sources, TAB_URL), line: 16 }); + info("Breakpoint was set."); + dbgWin.DebuggerController._target.activeTab.reload(); + info("Page reloaded."); + yield waitForSourceAndCaretAndScopes(panel, ".html", 16); + yield ensureThreadClientState(panel, "paused"); + info("Breakpoint was hit."); + EventUtils.sendMouseEvent({ type: "mousedown" }, + frames.selectedItem.target, + dbgWin); + info("The breadcrumb received focus."); + + // This is the meat of the test. + let jsterm = yield getSplitConsole(toolbox); + + is(dbgWin.gThreadClient.state, "paused", "Execution is still paused."); + + let dbgFrameConsoleEvalResult = yield jsterm.execute("privateVar"); + + is( + dbgFrameConsoleEvalResult.querySelector(".console-string").textContent, + '"privateVarValue"', + "Got the expected split console result on paused debugger" + ); + + yield dbgWin.gThreadClient.resume(); + + is(dbgWin.gThreadClient.state, "attached", "Execution is resumed."); + + // Get the last evaluation result adopted by the new debugger. + let mainTargetConsoleEvalResult = yield jsterm.execute("$_"); + + is( + mainTargetConsoleEvalResult.querySelector(".console-string").textContent, + '"privateVarValue"', + "Got the expected split console log on $_ executed on resumed debugger" + ); + + Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled"); + yield closeDebuggerAndFinish(panel); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-01.js new file mode 100644 index 000000000..513823ba9 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-01.js @@ -0,0 +1,49 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that stackframes are added when debugger is paused. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +var gTab, gPanel, gDebugger; +var gFrames, gClassicFrames; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gFrames = gDebugger.DebuggerView.StackFrames; + gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList; + + waitForCaretAndScopes(gPanel, 14).then(performTest); + callInTab(gTab, "simpleCall"); + }); +} + +function performTest() { + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(gFrames.itemCount, 1, + "Should have only one frame."); + is(gClassicFrames.itemCount, 1, + "Should also have only one frame in the mirrored view."); + + resumeDebuggerThenCloseAndFinish(gPanel); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gFrames = null; + gClassicFrames = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-02.js new file mode 100644 index 000000000..5c972f4ee --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-02.js @@ -0,0 +1,115 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that stackframes are added when debugger is paused in eval calls. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gFrames = gDebugger.DebuggerView.StackFrames; + const gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList; + + const performTest = Task.async(function* () { + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(gFrames.itemCount, 2, + "Should have two frames."); + is(gClassicFrames.itemCount, 2, + "Should also have only two in the mirrored view."); + + is(gFrames.getItemAtIndex(0).attachment.title, + "evalCall", "Oldest frame name should be correct."); + is(gFrames.getItemAtIndex(0).attachment.url, + TAB_URL, "Oldest frame url should be correct."); + is(gClassicFrames.getItemAtIndex(0).attachment.depth, + 0, "Oldest frame name is mirrored correctly."); + + is(gFrames.getItemAtIndex(1).attachment.title, + "(eval)", "Newest frame name should be correct."); + is(gFrames.getItemAtIndex(1).attachment.url, + "SCRIPT0", "Newest frame url should be correct."); + is(gClassicFrames.getItemAtIndex(1).attachment.depth, + 1, "Newest frame name is mirrored correctly."); + + is(gFrames.selectedIndex, 1, + "Newest frame should be selected by default."); + is(gClassicFrames.selectedIndex, 0, + "Newest frame should be selected by default in the mirrored view."); + + isnot(gFrames.selectedIndex, 0, + "Oldest frame should not be selected."); + isnot(gClassicFrames.selectedIndex, 1, + "Oldest frame should not be selected in the mirrored view."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gFrames.getItemAtIndex(0).target, + gDebugger); + + isnot(gFrames.selectedIndex, 1, + "Newest frame should not be selected after click."); + isnot(gClassicFrames.selectedIndex, 0, + "Newest frame in the mirrored view should not be selected."); + + is(gFrames.selectedIndex, 0, + "Oldest frame should be selected after click."); + is(gClassicFrames.selectedIndex, 1, + "Oldest frame in the mirrored view should be selected."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gFrames.getItemAtIndex(1).target.querySelector(".dbg-stackframe-title"), + gDebugger); + // Give the UI some time to update. For some reason if we don't + // do this there is global window leakage. We are continually + // cleaning up our tests so this will be refactored out at some + // point. + yield waitForTime(1); + + is(gFrames.selectedIndex, 1, + "Newest frame should be selected after click inside the newest frame."); + is(gClassicFrames.selectedIndex, 0, + "Newest frame in the mirrored view should be selected."); + + isnot(gFrames.selectedIndex, 0, + "Oldest frame should not be selected after click inside the newest frame."); + isnot(gClassicFrames.selectedIndex, 1, + "Oldest frame in the mirrored view should not be selected."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gFrames.getItemAtIndex(0).target.querySelector(".dbg-stackframe-details"), + gDebugger); + // See comment above on the same statement. + yield waitForTime(1); + + isnot(gFrames.selectedIndex, 1, + "Newest frame should not be selected after click inside the oldest frame."); + isnot(gClassicFrames.selectedIndex, 0, + "Newest frame in the mirrored view should not be selected."); + + is(gFrames.selectedIndex, 0, + "Oldest frame should be selected after click inside the oldest frame."); + is(gClassicFrames.selectedIndex, 1, + "Oldest frame in the mirrored view should be selected."); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); + + Task.spawn(function* () { + yield waitForCaretAndScopes(gPanel, 1); + performTest(); + }); + + callInTab(gTab, "evalCall"); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-03.js new file mode 100644 index 000000000..28993bfd5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-03.js @@ -0,0 +1,64 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that stackframes are scrollable. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; +let framesScrollingInterval; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab, aDebuggee, aPanel]) => { + const tab = aTab; + const debuggee = aDebuggee; + const panel = aPanel; + const gDebugger = panel.panelWin; + const frames = gDebugger.DebuggerView.StackFrames; + const classicFrames = gDebugger.DebuggerView.StackFramesClassicList; + + Task.spawn(function* () { + framesScrollingInterval = window.setInterval(() => { + frames.widget._list.scrollByIndex(-1); + }, 100); + + yield waitForDebuggerEvents(panel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED); + + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(frames.itemCount, gDebugger.gCallStackPageSize, + "Should have only the max limit of frames."); + is(classicFrames.itemCount, gDebugger.gCallStackPageSize, + "Should have only the max limit of frames in the mirrored view as well."); + + yield waitForDebuggerEvents(panel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED); + + is(frames.itemCount, gDebugger.gCallStackPageSize * 2, + "Should now have twice the max limit of frames."); + is(classicFrames.itemCount, gDebugger.gCallStackPageSize * 2, + "Should now have twice the max limit of frames in the mirrored view as well."); + + yield waitForDebuggerEvents(panel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED); + + is(frames.itemCount, debuggee.gRecurseLimit, + "Should have reached the recurse limit."); + is(classicFrames.itemCount, debuggee.gRecurseLimit, + "Should have reached the recurse limit in the mirrored view as well."); + + + // Call stack frame scrolling should stop before + // we resume the gDebugger as it could be a source of race conditions. + window.clearInterval(framesScrollingInterval); + resumeDebuggerThenCloseAndFinish(panel); + }); + + debuggee.gRecurseLimit = (gDebugger.gCallStackPageSize * 2) + 1; + debuggee.recurse(); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-04.js new file mode 100644 index 000000000..808ec634e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-04.js @@ -0,0 +1,58 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that stackframes are cleared after resume. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +var gTab, gPanel, gDebugger; +var gFrames, gClassicFrames; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gFrames = gDebugger.DebuggerView.StackFrames; + gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList; + + waitForCaretAndScopes(gPanel, 1).then(performTest); + callInTab(gTab, "evalCall"); + }); +} + +function performTest() { + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(gFrames.itemCount, 2, + "Should have two frames."); + is(gClassicFrames.itemCount, 2, + "Should also have two frames in the mirrored view."); + + gDebugger.once(gDebugger.EVENTS.AFTER_FRAMES_CLEARED, () => { + is(gFrames.itemCount, 0, + "Should have no frames after resume."); + is(gClassicFrames.itemCount, 0, + "Should also have no frames in the mirrored view after resume."); + + closeDebuggerAndFinish(gPanel); + }, true); + + gDebugger.gThreadClient.resume(); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gFrames = null; + gClassicFrames = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-05.js new file mode 100644 index 000000000..2e5648922 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-05.js @@ -0,0 +1,102 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that switching between stack frames properly sets the current debugger + * location in the source editor. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gEditor = gDebugger.DebuggerView.editor; + const gSources = gDebugger.DebuggerView.Sources; + const gFrames = gDebugger.DebuggerView.StackFrames; + const gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList; + + function initialChecks() { + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(gFrames.itemCount, 2, + "Should have four frames."); + is(gClassicFrames.itemCount, 2, + "Should also have four frames in the mirrored view."); + } + + function testNewestFrame() { + is(gFrames.selectedIndex, 1, + "Newest frame should be selected by default."); + is(gClassicFrames.selectedIndex, 0, + "Newest frame should be selected in the mirrored view as well."); + is(gSources.selectedIndex, 1, + "The second source is selected in the widget."); + ok(isCaretPos(gPanel, 6), + "Editor caret location is correct."); + is(gEditor.getDebugLocation(), 5, + "Editor debug location is correct."); + } + + function testOldestFrame() { + const shown = waitForSourceAndCaret(gPanel, "-01.js", 5).then(() => { + is(gFrames.selectedIndex, 0, + "Second frame should be selected after click."); + is(gClassicFrames.selectedIndex, 1, + "Second frame should be selected in the mirrored view as well."); + is(gSources.selectedIndex, 0, + "The first source is now selected in the widget."); + ok(isCaretPos(gPanel, 5), + "Editor caret location is correct (3)."); + is(gEditor.getDebugLocation(), 4, + "Editor debug location is correct."); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.querySelector("#stackframe-1"), + gDebugger); + + return shown; + } + + function testAfterResume() { + let deferred = promise.defer(); + + gDebugger.once(gDebugger.EVENTS.AFTER_FRAMES_CLEARED, () => { + is(gFrames.itemCount, 0, + "Should have no frames after resume."); + is(gClassicFrames.itemCount, 0, + "Should have no frames in the mirrored view as well."); + ok(isCaretPos(gPanel, 5), + "Editor caret location is correct after resume."); + is(gEditor.getDebugLocation(), null, + "Editor debug location is correct after resume."); + + deferred.resolve(); + }, true); + + gDebugger.gThreadClient.resume(); + + return deferred.promise; + } + + Task.spawn(function* () { + yield waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6); + yield initialChecks(); + yield testNewestFrame(); + yield testOldestFrame(); + yield testAfterResume(); + closeDebuggerAndFinish(gPanel); + }); + + callInTab(gTab, "firstCall"); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-06.js new file mode 100644 index 000000000..11f3b9534 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-06.js @@ -0,0 +1,92 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that selecting a stack frame loads the right source in the editor + * pane and highlights the proper line. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources, gFrames, gClassicFrames; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gFrames = gDebugger.DebuggerView.StackFrames; + gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList; + + waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest); + callInTab(gTab, "firstCall"); + }); +} + +function performTest() { + is(gFrames.selectedIndex, 1, + "Newest frame should be selected by default."); + is(gClassicFrames.selectedIndex, 0, + "Newest frame should also be selected in the mirrored view."); + is(gSources.selectedIndex, 1, + "The second source is selected in the widget."); + is(gEditor.getText().search(/firstCall/), -1, + "The first source is not displayed."); + is(gEditor.getText().search(/debugger/), 166, + "The second source is displayed."); + + waitForSourceAndCaret(gPanel, "-01.js", 5).then(waitForTick).then(() => { + is(gFrames.selectedIndex, 0, + "Oldest frame should be selected after click."); + is(gClassicFrames.selectedIndex, 1, + "Oldest frame should also be selected in the mirrored view."); + is(gSources.selectedIndex, 0, + "The first source is now selected in the widget."); + is(gEditor.getText().search(/firstCall/), 118, + "The first source is displayed."); + is(gEditor.getText().search(/debugger/), -1, + "The second source is not displayed."); + + waitForSourceAndCaret(gPanel, "-02.js", 6).then(waitForTick).then(() => { + is(gFrames.selectedIndex, 1, + "Newest frame should be selected again after click."); + is(gClassicFrames.selectedIndex, 0, + "Newest frame should also be selected again in the mirrored view."); + is(gSources.selectedIndex, 1, + "The second source is selected in the widget."); + is(gEditor.getText().search(/firstCall/), -1, + "The first source is not displayed."); + is(gEditor.getText().search(/debugger/), 166, + "The second source is displayed."); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.querySelector("#classic-stackframe-0"), + gDebugger); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.querySelector("#stackframe-1"), + gDebugger); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; + gFrames = null; + gClassicFrames = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-07.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-07.js new file mode 100644 index 000000000..a5bbc5a10 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-07.js @@ -0,0 +1,113 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that after selecting a different stack frame, resuming reselects + * the topmost stackframe, loads the right source in the editor pane and + * highlights the proper line. + */ + +const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gSources, gFrames, gClassicFrames, gToolbar; + +function test() { + let options = { + source: EXAMPLE_URL + "code_script-switching-01.js", + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gSources = gDebugger.DebuggerView.Sources; + gFrames = gDebugger.DebuggerView.StackFrames; + gClassicFrames = gDebugger.DebuggerView.StackFramesClassicList; + gToolbar = gDebugger.DebuggerView.Toolbar; + + waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest); + callInTab(gTab, "firstCall"); + }); +} + +function performTest() { + return Task.spawn(function* () { + yield selectBottomFrame(); + testBottomFrame(4); + + yield performStep("StepOver"); + testTopFrame(1); + + yield selectBottomFrame(); + testBottomFrame(4); + + yield performStep("StepIn"); + testTopFrame(1); + + yield selectBottomFrame(); + testBottomFrame(4); + + yield performStep("StepOut"); + testTopFrame(1); + + yield resumeDebuggerThenCloseAndFinish(gPanel); + }); + + function selectBottomFrame() { + let shown = waitForSourceShown(gPanel, "-01.js"); + gClassicFrames.selectedIndex = gClassicFrames.itemCount - 1; + return shown; + } + + function testBottomFrame(debugLocation) { + is(gFrames.selectedIndex, 0, + "Oldest frame should be selected after click."); + is(gClassicFrames.selectedIndex, gFrames.itemCount - 1, + "Oldest frame should also be selected in the mirrored view."); + is(gSources.selectedIndex, 0, + "The first source is now selected in the widget."); + is(gEditor.getText().search(/firstCall/), 118, + "The first source is displayed."); + is(gEditor.getText().search(/debugger/), -1, + "The second source is not displayed."); + + is(gEditor.getDebugLocation(), debugLocation, + "Editor debugger location is correct."); + ok(gEditor.hasLineClass(debugLocation, "debug-line"), + "The debugged line is highlighted appropriately."); + } + + function performStep(type) { + let updated = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES); + gToolbar["_on" + type + "Pressed"](); + return updated.then(waitForTick); + } + + function testTopFrame(frameIndex) { + is(gFrames.selectedIndex, frameIndex, + "Topmost frame should be selected after click."); + is(gClassicFrames.selectedIndex, gFrames.itemCount - frameIndex - 1, + "Topmost frame should also be selected in the mirrored view."); + is(gSources.selectedIndex, 1, + "The second source is now selected in the widget."); + is(gEditor.getText().search(/firstCall/), -1, + "The second source is displayed."); + is(gEditor.getText().search(/debugger/), 166, + "The first source is not displayed."); + } +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gSources = null; + gFrames = null; + gClassicFrames = null; + gToolbar = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-01.js new file mode 100644 index 000000000..61d964b91 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-01.js @@ -0,0 +1,58 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the copy contextmenu has been added to the stack frames view. + */ + + const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + let gTab, gPanel, gDebugger; + let gFrames, gContextMenu; + + function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gFrames = gDebugger.DebuggerView.StackFrames; + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED) + .then(performTest); + callInTab(gTab, "simpleCall"); + }); + } + + function performTest() { + gContextMenu = gDebugger.document.getElementById("stackFramesContextMenu"); + is(gDebugger.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(gFrames.itemCount, 1, + "Should have only one frame."); + ok(gContextMenu, "The stack frame's context menupopup is available."); + + once(gContextMenu, "popupshown").then(testContextMenu); + EventUtils.synthesizeMouseAtCenter(gFrames.getItemAtIndex(0).prebuiltNode, {type: "contextmenu", button: 2}, gDebugger); + } + + function testContextMenu() { + let document = gDebugger.document; + ok(document.getElementById("copyStackMenuItem"), + "#copyStackMenuItem found."); + + gContextMenu.hidePopup(); + resumeDebuggerThenCloseAndFinish(gPanel); + } + + registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gFrames = null; + gContextMenu = null; + }); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-02.js new file mode 100644 index 000000000..828bce6c8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-02.js @@ -0,0 +1,58 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that the copy contextmenu copys the stack frames to the clipboard. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; +const STACK_STRING = "simpleCall@" + EXAMPLE_URL + "doc_recursion-stack.html:14:8"; + +function test() { + let gTab, gPanel, gDebugger, gFrames; + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gFrames = gDebugger.DebuggerView.StackFrames; + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED) + .then(openContextMenu) + .then(testCopyStackMenuItem) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + callInTab(gTab, "simpleCall"); + }); + + function clickCopyStack() { + return new Promise((resolve, reject) => { + let copyStackMenuItem = gDebugger.document.getElementById("copyStackMenuItem"); + if (!copyStackMenuItem) { + reject(new Error("The Copy stack context menu item is not available.")); + } + + ok(copyStackMenuItem, "The Copy stack context menu item is available."); + EventUtils.synthesizeMouseAtCenter(copyStackMenuItem, {}, gDebugger); + resolve(); + }); + } + + function testCopyStackMenuItem() { + return waitForClipboardPromise(clickCopyStack, STACK_STRING); + } + + function openContextMenu() { + let contextMenu = gDebugger.document.getElementById("stackFramesContextMenu"); + let contextMenuShown = once(contextMenu, "popupshown"); + EventUtils.synthesizeMouseAtCenter(gFrames.getItemAtIndex(0).prebuiltNode, {type: "contextmenu", button: 2}, gDebugger); + return contextMenuShown; + } +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_step-out.js b/devtools/client/debugger/test/mochitest/browser_dbg_step-out.js new file mode 100644 index 000000000..ae1099a92 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_step-out.js @@ -0,0 +1,91 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that stepping out of a function displays the right return value. + */ + +const TAB_URL = EXAMPLE_URL + "doc_step-out.html"; + +var gTab, gPanel, gDebugger; +var gVars; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVars = gDebugger.DebuggerView.Variables; + + testNormalReturn(); + }); +} + +function testNormalReturn() { + waitForCaretAndScopes(gPanel, 17).then(() => { + waitForCaretAndScopes(gPanel, 20).then(() => { + let innerScope = gVars.getScopeAtIndex(0); + let returnVar = innerScope.get("<return>"); + + is(returnVar.name, "<return>", + "Should have the right property name for the returned value."); + is(returnVar.value, 10, + "Should have the right property value for the returned value."); + ok(returnVar._internalItem, "Should be an internal item"); + ok(returnVar._target.hasAttribute("pseudo-item"), + "Element should be marked as a pseudo-item"); + + resumeDebuggee().then(() => testReturnWithException()); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("step-out"), + gDebugger); + }); + + generateMouseClickInTab(gTab, "content.document.getElementById('return')"); +} + +function testReturnWithException() { + waitForCaretAndScopes(gPanel, 24).then(() => { + waitForCaretAndScopes(gPanel, 26).then(() => { + let innerScope = gVars.getScopeAtIndex(0); + let exceptionVar = innerScope.get("<exception>"); + + is(exceptionVar.name, "<exception>", + "Should have the right property name for the returned value."); + is(exceptionVar.value, "boom", + "Should have the right property value for the returned value."); + ok(exceptionVar._internalItem, "Should be an internal item"); + ok(exceptionVar._target.hasAttribute("pseudo-item"), + "Element should be marked as a pseudo-item"); + + resumeDebuggee().then(() => closeDebuggerAndFinish(gPanel)); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("step-out"), + gDebugger); + }); + + generateMouseClickInTab(gTab, "content.document.getElementById('throw')"); +} + +function resumeDebuggee() { + let deferred = promise.defer(); + gDebugger.gThreadClient.resume(deferred.resolve); + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVars = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-01.js new file mode 100644 index 000000000..dfb073617 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-01.js @@ -0,0 +1,65 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check extension-added tab actor lifetimes. + */ + +const ACTORS_URL = CHROME_URL + "testactors.js"; +const TAB_URL = EXAMPLE_URL + "doc_empty-tab-01.html"; + +var gClient; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + DebuggerServer.addActors(ACTORS_URL); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + addTab(TAB_URL) + .then(() => attachTabActorForUrl(gClient, TAB_URL)) + .then(testTabActor) + .then(closeTab) + .then(() => gClient.close()) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testTabActor([aGrip, aResponse]) { + let deferred = promise.defer(); + + ok(aGrip.testTabActor1, + "Found the test tab actor."); + ok(aGrip.testTabActor1.includes("test_one"), + "testTabActor1's actorPrefix should be used."); + + gClient.request({ to: aGrip.testTabActor1, type: "ping" }, aResponse => { + is(aResponse.pong, "pong", + "Actor should respond to requests."); + + deferred.resolve(); + }); + + return deferred.promise; +} + +function closeTab() { + return removeTab(gBrowser.selectedTab); +} + +registerCleanupFunction(function () { + gClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-02.js new file mode 100644 index 000000000..c9f506db2 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-02.js @@ -0,0 +1,79 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check extension-added tab actor lifetimes. + */ + +const ACTORS_URL = CHROME_URL + "testactors.js"; +const TAB_URL = EXAMPLE_URL + "doc_empty-tab-01.html"; + +var gClient; + +function test() { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + DebuggerServer.addActors(ACTORS_URL); + + let transport = DebuggerServer.connectPipe(); + gClient = new DebuggerClient(transport); + gClient.connect().then(([aType, aTraits]) => { + is(aType, "browser", + "Root actor should identify itself as a browser."); + + addTab(TAB_URL) + .then(() => attachTabActorForUrl(gClient, TAB_URL)) + .then(testTabActor) + .then(closeTab) + .then(() => gClient.close()) + .then(finish) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function testTabActor([aGrip, aResponse]) { + let deferred = promise.defer(); + + ok(aGrip.testTabActor1, + "Found the test tab actor."); + ok(aGrip.testTabActor1.includes("test_one"), + "testTabActor1's actorPrefix should be used."); + + gClient.request({ to: aGrip.testTabActor1, type: "ping" }, aResponse => { + is(aResponse.pong, "pong", + "Actor should respond to requests."); + + deferred.resolve(aResponse.actor); + }); + + return deferred.promise; +} + +function closeTab(aTestActor) { + return removeTab(gBrowser.selectedTab).then(() => { + let deferred = promise.defer(); + + try { + gClient.request({ to: aTestActor, type: "ping" }, aResponse => { + ok(false, "testTabActor1 didn't go away with the tab."); + deferred.reject(aResponse); + }); + } catch (e) { + is(e.message, "'ping' request packet has no destination.", "testTabActor1 went away."); + deferred.resolve(); + } + + return deferred.promise; + }); +} + +registerCleanupFunction(function () { + gClient = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_terminate-on-tab-close.js b/devtools/client/debugger/test/mochitest/browser_dbg_terminate-on-tab-close.js new file mode 100644 index 000000000..42a1e6c70 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_terminate-on-tab-close.js @@ -0,0 +1,34 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejections should be fixed. +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("[object Object]"); + +/** + * Tests that debuggee scripts are terminated on tab closure. + */ + +const TAB_URL = EXAMPLE_URL + "doc_terminate-on-tab-close.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + + gDebugger.gThreadClient.addOneTimeListener("paused", () => { + resumeDebuggerThenCloseAndFinish(gPanel).then(function () { + ok(true, "should not throw after this point"); + }); + }); + + callInTab(gTab, "debuggerThenThrow"); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-01.js new file mode 100644 index 000000000..c5c846978 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-01.js @@ -0,0 +1,132 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that creating, collpasing and expanding scopes in the + * variables view works as expected. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + let variables = aPanel.panelWin.DebuggerView.Variables; + let testScope = variables.addScope("test"); + + ok(testScope, + "Should have created a scope."); + ok(testScope.id.includes("test"), + "The newly created scope should have the default id set."); + is(testScope.name, "test", + "The newly created scope should have the desired name set."); + + ok(!testScope.displayValue, + "The newly created scope should not have a displayed value (1)."); + ok(!testScope.displayValueClassName, + "The newly created scope should not have a displayed value (2)."); + + ok(testScope.target, + "The newly created scope should point to a target node."); + ok(testScope.target.id.includes("test"), + "Should have the correct scope id on the element."); + + is(testScope.target.querySelector(".name").getAttribute("value"), "test", + "Any new scope should have the designated name."); + is(testScope.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0, + "Any new scope should have a container with no enumerable child nodes."); + is(testScope.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0, + "Any new scope should have a container with no non-enumerable child nodes."); + + ok(!testScope.expanded, + "Any new created scope should be initially collapsed."); + ok(testScope.visible, + "Any new created scope should be initially visible."); + + let expandCallbackArg = null; + let collapseCallbackArg = null; + let toggleCallbackArg = null; + let hideCallbackArg = null; + let showCallbackArg = null; + + testScope.onexpand = aScope => expandCallbackArg = aScope; + testScope.oncollapse = aScope => collapseCallbackArg = aScope; + testScope.ontoggle = aScope => toggleCallbackArg = aScope; + testScope.onhide = aScope => hideCallbackArg = aScope; + testScope.onshow = aScope => showCallbackArg = aScope; + + testScope.expand(); + ok(testScope.expanded, + "The testScope shouldn't be collapsed anymore."); + is(expandCallbackArg, testScope, + "The expandCallback wasn't called as it should."); + + testScope.collapse(); + ok(!testScope.expanded, + "The testScope should be collapsed again."); + is(collapseCallbackArg, testScope, + "The collapseCallback wasn't called as it should."); + + testScope.expanded = true; + ok(testScope.expanded, + "The testScope shouldn't be collapsed anymore."); + + testScope.toggle(); + ok(!testScope.expanded, + "The testScope should be collapsed again."); + is(toggleCallbackArg, testScope, + "The toggleCallback wasn't called as it should."); + + testScope.hide(); + ok(!testScope.visible, + "The testScope should be invisible after hiding."); + is(hideCallbackArg, testScope, + "The hideCallback wasn't called as it should."); + + testScope.show(); + ok(testScope.visible, + "The testScope should be visible again."); + is(showCallbackArg, testScope, + "The showCallback wasn't called as it should."); + + testScope.visible = false; + ok(!testScope.visible, + "The testScope should be invisible after hiding."); + ok(!testScope.expanded, + "The testScope should remember it is collapsed even if it is hidden."); + + testScope.visible = true; + ok(testScope.visible, + "The testScope should be visible after reshowing."); + ok(!testScope.expanded, + "The testScope should remember it is collapsed after it is reshown."); + + EventUtils.sendMouseEvent({ type: "mousedown", button: 1 }, + testScope.target.querySelector(".title"), + aPanel.panelWin); + + ok(!testScope.expanded, + "Clicking the testScope title with the right mouse button should't expand it."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + testScope.target.querySelector(".title"), + aPanel.panelWin); + + ok(testScope.expanded, + "Clicking the testScope title should expand it."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + testScope.target.querySelector(".title"), + aPanel.panelWin); + + ok(!testScope.expanded, + "Clicking again the testScope title should collapse it."); + + closeDebuggerAndFinish(aPanel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-02.js new file mode 100644 index 000000000..f97353dba --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-02.js @@ -0,0 +1,227 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that creating, collapsing and expanding variables in the + * variables view works as expected. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + let variables = aPanel.panelWin.DebuggerView.Variables; + let testScope = variables.addScope("test"); + let testVar = testScope.addItem("something"); + let duplVar = testScope.addItem("something"); + + info("Scope id: " + testScope.id); + info("Scope name: " + testScope.name); + info("Variable id: " + testVar.id); + info("Variable name: " + testVar.name); + + ok(testScope, + "Should have created a scope."); + is(duplVar, testVar, + "Shouldn't be able to duplicate variables in the same scope."); + + ok(testVar, + "Should have created a variable."); + ok(testVar.id.includes("something"), + "The newly created variable should have the default id set."); + is(testVar.name, "something", + "The newly created variable should have the desired name set."); + + ok(!testVar.displayValue, + "The newly created variable should not have a displayed value yet (1)."); + ok(!testVar.displayValueClassName, + "The newly created variable should not have a displayed value yet (2)."); + + ok(testVar.target, + "The newly created scope should point to a target node."); + ok(testVar.target.id.includes("something"), + "Should have the correct variable id on the element."); + + is(testVar.target.querySelector(".name").getAttribute("value"), "something", + "Any new variable should have the designated name."); + is(testVar.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0, + "Any new variable should have a container with no enumerable child nodes."); + is(testVar.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0, + "Any new variable should have a container with no non-enumerable child nodes."); + + ok(!testVar.expanded, + "Any new created scope should be initially collapsed."); + ok(testVar.visible, + "Any new created scope should be initially visible."); + + let expandCallbackArg = null; + let collapseCallbackArg = null; + let toggleCallbackArg = null; + let hideCallbackArg = null; + let showCallbackArg = null; + + testVar.onexpand = aScope => expandCallbackArg = aScope; + testVar.oncollapse = aScope => collapseCallbackArg = aScope; + testVar.ontoggle = aScope => toggleCallbackArg = aScope; + testVar.onhide = aScope => hideCallbackArg = aScope; + testVar.onshow = aScope => showCallbackArg = aScope; + + testVar.expand(); + ok(testVar.expanded, + "The testVar shouldn't be collapsed anymore."); + is(expandCallbackArg, testVar, + "The expandCallback wasn't called as it should."); + + testVar.collapse(); + ok(!testVar.expanded, + "The testVar should be collapsed again."); + is(collapseCallbackArg, testVar, + "The collapseCallback wasn't called as it should."); + + testVar.expanded = true; + ok(testVar.expanded, + "The testVar shouldn't be collapsed anymore."); + + testVar.toggle(); + ok(!testVar.expanded, + "The testVar should be collapsed again."); + is(toggleCallbackArg, testVar, + "The toggleCallback wasn't called as it should."); + + testVar.hide(); + ok(!testVar.visible, + "The testVar should be invisible after hiding."); + is(hideCallbackArg, testVar, + "The hideCallback wasn't called as it should."); + + testVar.show(); + ok(testVar.visible, + "The testVar should be visible again."); + is(showCallbackArg, testVar, + "The showCallback wasn't called as it should."); + + testVar.visible = false; + ok(!testVar.visible, + "The testVar should be invisible after hiding."); + ok(!testVar.expanded, + "The testVar should remember it is collapsed even if it is hidden."); + + testVar.visible = true; + ok(testVar.visible, + "The testVar should be visible after reshowing."); + ok(!testVar.expanded, + "The testVar should remember it is collapsed after it is reshown."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + testVar.target.querySelector(".name"), + aPanel.panelWin); + + ok(testVar.expanded, + "Clicking the testVar name should expand it."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + testVar.target.querySelector(".name"), + aPanel.panelWin); + + ok(!testVar.expanded, + "Clicking again the testVar name should collapse it."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + testVar.target.querySelector(".arrow"), + aPanel.panelWin); + + ok(testVar.expanded, + "Clicking the testVar arrow should expand it."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + testVar.target.querySelector(".arrow"), + aPanel.panelWin); + + ok(!testVar.expanded, + "Clicking again the testVar arrow should collapse it."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + testVar.target.querySelector(".title"), + aPanel.panelWin); + + ok(testVar.expanded, + "Clicking the testVar title should expand it again."); + + testVar.addItem("child", { + value: { + type: "object", + class: "Object" + } + }); + + let testChild = testVar.get("child"); + ok(testChild, + "Should have created a child property."); + ok(testChild.id.includes("child"), + "The newly created property should have the default id set."); + is(testChild.name, "child", + "The newly created property should have the desired name set."); + + is(testChild.displayValue, "Object", + "The newly created property should not have a displayed value yet (1)."); + is(testChild.displayValueClassName, "token-other", + "The newly created property should not have a displayed value yet (2)."); + + ok(testChild.target, + "The newly created scope should point to a target node."); + ok(testChild.target.id.includes("child"), + "Should have the correct property id on the element."); + + is(testChild.target.querySelector(".name").getAttribute("value"), "child", + "Any new property should have the designated name."); + is(testChild.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0, + "Any new property should have a container with no enumerable child nodes."); + is(testChild.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0, + "Any new property should have a container with no non-enumerable child nodes."); + + ok(!testChild.expanded, + "Any new created scope should be initially collapsed."); + ok(testChild.visible, + "Any new created scope should be initially visible."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + testChild.target.querySelector(".name"), + aPanel.panelWin); + + ok(testChild.expanded, + "Clicking the testChild name should expand it."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + testChild.target.querySelector(".name"), + aPanel.panelWin); + + ok(!testChild.expanded, + "Clicking again the testChild name should collapse it."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + testChild.target.querySelector(".arrow"), + aPanel.panelWin); + + ok(testChild.expanded, + "Clicking the testChild arrow should expand it."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + testChild.target.querySelector(".arrow"), + aPanel.panelWin); + + ok(!testChild.expanded, + "Clicking again the testChild arrow should collapse it."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + testChild.target.querySelector(".title"), + aPanel.panelWin); + + closeDebuggerAndFinish(aPanel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-03.js new file mode 100644 index 000000000..64e4d45a2 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-03.js @@ -0,0 +1,157 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that recursively creating properties in the variables view works + * as expected. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + let variables = aPanel.panelWin.DebuggerView.Variables; + let testScope = variables.addScope("test"); + + is(testScope.target.querySelectorAll(".variables-view-element-details.enum").length, 1, + "One enumerable container should be present in the scope."); + is(testScope.target.querySelectorAll(".variables-view-element-details.nonenum").length, 1, + "One non-enumerable container should be present in the scope."); + is(testScope.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0, + "No enumerable variables should be present in the scope."); + is(testScope.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0, + "No non-enumerable variables should be present in the scope."); + + testScope.addItem("something", { + value: { + type: "object", + class: "Object" + }, + enumerable: true + }); + + is(testScope.target.querySelectorAll(".variables-view-element-details.enum").length, 2, + "Two enumerable containers should be present in the tree."); + is(testScope.target.querySelectorAll(".variables-view-element-details.nonenum").length, 2, + "Two non-enumerable containers should be present in the tree."); + + is(testScope.target.querySelector(".variables-view-element-details.enum").childNodes.length, 1, + "A new enumerable variable should have been added in the scope."); + is(testScope.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0, + "No new non-enumerable variables should have been added in the scope."); + + let testVar = testScope.get("something"); + ok(testVar, + "The added variable should be accessible from the scope."); + + is(testVar.target.querySelectorAll(".variables-view-element-details.enum").length, 1, + "One enumerable container should be present in the variable."); + is(testVar.target.querySelectorAll(".variables-view-element-details.nonenum").length, 1, + "One non-enumerable container should be present in the variable."); + is(testVar.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0, + "No enumerable properties should be present in the variable."); + is(testVar.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0, + "No non-enumerable properties should be present in the variable."); + + testVar.addItem("child", { + value: { + type: "object", + class: "Object" + }, + enumerable: true + }); + + is(testScope.target.querySelectorAll(".variables-view-element-details.enum").length, 3, + "Three enumerable containers should be present in the tree."); + is(testScope.target.querySelectorAll(".variables-view-element-details.nonenum").length, 3, + "Three non-enumerable containers should be present in the tree."); + + is(testVar.target.querySelector(".variables-view-element-details.enum").childNodes.length, 1, + "A new enumerable property should have been added in the variable."); + is(testVar.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0, + "No new non-enumerable properties should have been added in the variable."); + + let testChild = testVar.get("child"); + ok(testChild, + "The added property should be accessible from the variable."); + + is(testChild.target.querySelectorAll(".variables-view-element-details.enum").length, 1, + "One enumerable container should be present in the property."); + is(testChild.target.querySelectorAll(".variables-view-element-details.nonenum").length, 1, + "One non-enumerable container should be present in the property."); + is(testChild.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0, + "No enumerable sub-properties should be present in the property."); + is(testChild.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0, + "No non-enumerable sub-properties should be present in the property."); + + testChild.addItem("grandChild", { + value: { + type: "object", + class: "Object" + }, + enumerable: true + }); + + is(testScope.target.querySelectorAll(".variables-view-element-details.enum").length, 4, + "Four enumerable containers should be present in the tree."); + is(testScope.target.querySelectorAll(".variables-view-element-details.nonenum").length, 4, + "Four non-enumerable containers should be present in the tree."); + + is(testChild.target.querySelector(".variables-view-element-details.enum").childNodes.length, 1, + "A new enumerable sub-property should have been added in the property."); + is(testChild.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0, + "No new non-enumerable sub-properties should have been added in the property."); + + let testGrandChild = testChild.get("grandChild"); + ok(testGrandChild, + "The added sub-property should be accessible from the property."); + + is(testGrandChild.target.querySelectorAll(".variables-view-element-details.enum").length, 1, + "One enumerable container should be present in the property."); + is(testGrandChild.target.querySelectorAll(".variables-view-element-details.nonenum").length, 1, + "One non-enumerable container should be present in the property."); + is(testGrandChild.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0, + "No enumerable sub-properties should be present in the property."); + is(testGrandChild.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0, + "No non-enumerable sub-properties should be present in the property."); + + testGrandChild.addItem("granderChild", { + value: { + type: "object", + class: "Object" + }, + enumerable: true + }); + + is(testScope.target.querySelectorAll(".variables-view-element-details.enum").length, 5, + "Five enumerable containers should be present in the tree."); + is(testScope.target.querySelectorAll(".variables-view-element-details.nonenum").length, 5, + "Five non-enumerable containers should be present in the tree."); + + is(testGrandChild.target.querySelector(".variables-view-element-details.enum").childNodes.length, 1, + "A new enumerable variable should have been added in the variable."); + is(testGrandChild.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0, + "No new non-enumerable variables should have been added in the variable."); + + let testGranderChild = testGrandChild.get("granderChild"); + ok(testGranderChild, + "The added sub-property should be accessible from the property."); + + is(testGranderChild.target.querySelectorAll(".variables-view-element-details.enum").length, 1, + "One enumerable container should be present in the property."); + is(testGranderChild.target.querySelectorAll(".variables-view-element-details.nonenum").length, 1, + "One non-enumerable container should be present in the property."); + is(testGranderChild.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0, + "No enumerable sub-properties should be present in the property."); + is(testGranderChild.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0, + "No non-enumerable sub-properties should be present in the property."); + + closeDebuggerAndFinish(aPanel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-04.js new file mode 100644 index 000000000..9db8b9cb8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-04.js @@ -0,0 +1,156 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that grips are correctly applied to variables. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + let variables = aPanel.panelWin.DebuggerView.Variables; + let testScope = variables.addScope("test"); + let testVar = testScope.addItem("something"); + + testVar.setGrip(1.618); + + is(testVar.target.querySelector(".value").getAttribute("value"), "1.618", + "The grip information for the variable wasn't set correctly (1)."); + is(testVar.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0, + "Setting the grip alone shouldn't add any new tree nodes (1)."); + is(testVar.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0, + "Setting the grip alone shouldn't add any new tree nodes (2)."); + + testVar.setGrip({ + type: "object", + class: "Window" + }); + + is(testVar.target.querySelector(".value").getAttribute("value"), "Window", + "The grip information for the variable wasn't set correctly (2)."); + is(testVar.target.querySelector(".variables-view-element-details.enum").childNodes.length, 0, + "Setting the grip alone shouldn't add any new tree nodes (3)."); + is(testVar.target.querySelector(".variables-view-element-details.nonenum").childNodes.length, 0, + "Setting the grip alone shouldn't add any new tree nodes (4)."); + + testVar.addItems({ + helloWorld: { + value: "hello world", + enumerable: true + } + }); + + is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 1, + "A new detail node should have been added in the variable tree."); + is(testVar.get("helloWorld").target.querySelector(".value").getAttribute("value"), "\"hello world\"", + "The grip information for the variable wasn't set correctly (3)."); + + testVar.addItems({ + helloWorld: { + value: "hello jupiter", + enumerable: true + } + }); + + is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 1, + "Shouldn't be able to duplicate nodes added in the variable tree."); + is(testVar.get("helloWorld").target.querySelector(".value").getAttribute("value"), "\"hello world\"", + "The grip information for the variable wasn't preserved correctly (4)."); + + testVar.addItems({ + someProp0: { + value: "random string", + enumerable: true + }, + someProp1: { + value: "another string", + enumerable: true + } + }); + + is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 3, + "Two new detail nodes should have been added in the variable tree."); + is(testVar.get("someProp0").target.querySelector(".value").getAttribute("value"), "\"random string\"", + "The grip information for the variable wasn't set correctly (5)."); + is(testVar.get("someProp1").target.querySelector(".value").getAttribute("value"), "\"another string\"", + "The grip information for the variable wasn't set correctly (6)."); + + testVar.addItems({ + someProp2: { + value: { + type: "null" + }, + enumerable: true + }, + someProp3: { + value: { + type: "undefined" + }, + enumerable: true + }, + someProp4: { + value: { + type: "object", + class: "Object" + }, + enumerable: true + } + }); + + is(testVar.target.querySelector(".variables-view-element-details").childNodes.length, 6, + "Three new detail nodes should have been added in the variable tree."); + is(testVar.get("someProp2").target.querySelector(".value").getAttribute("value"), "null", + "The grip information for the variable wasn't set correctly (7)."); + is(testVar.get("someProp3").target.querySelector(".value").getAttribute("value"), "undefined", + "The grip information for the variable wasn't set correctly (8)."); + is(testVar.get("someProp4").target.querySelector(".value").getAttribute("value"), "Object", + "The grip information for the variable wasn't set correctly (9)."); + + let parent = testVar.get("someProp2"); + let child = parent.addItem("child", { + value: { + type: "null" + } + }); + + is(variables.getItemForNode(parent.target), parent, + "VariablesView should have a record of the parent."); + is(variables.getItemForNode(child.target), child, + "VariablesView should have a record of the child."); + is([...parent].length, 1, + "Parent should have one child."); + + parent.remove(); + + is(variables.getItemForNode(parent.target), undefined, + "VariablesView should not have a record of the parent anymore."); + is(parent.target.parentNode, null, + "Parent element should not have a parent."); + is(variables.getItemForNode(child.target), undefined, + "VariablesView should not have a record of the child anymore."); + is(child.target.parentNode, null, + "Child element should not have a parent."); + is([...parent].length, 0, + "Parent should have zero children."); + + testScope.remove(); + + is([...variables].length, 0, + "VariablesView should have been emptied."); + is(ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(variables._itemsByElement).length, + 0, "VariablesView _itemsByElement map has been emptied."); + is(variables._currHierarchy.size, 0, + "VariablesView _currHierarchy map has been emptied."); + is(variables._list.children.length, 0, + "VariablesView element should have no children."); + + closeDebuggerAndFinish(aPanel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-05.js new file mode 100644 index 000000000..ebad7c4e2 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-05.js @@ -0,0 +1,234 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that grips are correctly applied to variables and properties. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + let variables = aPanel.panelWin.DebuggerView.Variables; + + let globalScope = variables.addScope("Test-Global"); + let localScope = variables.addScope("Test-Local"); + + ok(globalScope, "The globalScope hasn't been created correctly."); + ok(localScope, "The localScope hasn't been created correctly."); + + is(globalScope.target.querySelector(".separator"), null, + "No separator string should be created for scopes (1)."); + is(localScope.target.querySelector(".separator"), null, + "No separator string should be created for scopes (2)."); + + let windowVar = globalScope.addItem("window"); + let documentVar = globalScope.addItem("document"); + + ok(windowVar, "The windowVar hasn't been created correctly."); + ok(documentVar, "The documentVar hasn't been created correctly."); + + ok(windowVar.target.querySelector(".separator").hidden, + "No separator string should be shown for variables without a grip (1)."); + ok(documentVar.target.querySelector(".separator").hidden, + "No separator string should be shown for variables without a grip (2)."); + + windowVar.setGrip({ type: "object", class: "Window" }); + documentVar.setGrip({ type: "object", class: "HTMLDocument" }); + + is(windowVar.target.querySelector(".separator").hidden, false, + "A separator string should now be shown after setting the grip (1)."); + is(documentVar.target.querySelector(".separator").hidden, false, + "A separator string should now be shown after setting the grip (2)."); + + is(windowVar.target.querySelector(".separator").getAttribute("value"), ": ", + "The separator string label is correct (1)."); + is(documentVar.target.querySelector(".separator").getAttribute("value"), ": ", + "The separator string label is correct (2)."); + + let localVar0 = localScope.addItem("localVar0"); + let localVar1 = localScope.addItem("localVar1"); + let localVar2 = localScope.addItem("localVar2"); + let localVar3 = localScope.addItem("localVar3"); + let localVar4 = localScope.addItem("localVar4"); + let localVar5 = localScope.addItem("localVar5"); + + let localVar6 = localScope.addItem("localVar6"); + let localVar7 = localScope.addItem("localVar7"); + let localVar8 = localScope.addItem("localVar8"); + let localVar9 = localScope.addItem("localVar9"); + + ok(localVar0, "The localVar0 hasn't been created correctly."); + ok(localVar1, "The localVar1 hasn't been created correctly."); + ok(localVar2, "The localVar2 hasn't been created correctly."); + ok(localVar3, "The localVar3 hasn't been created correctly."); + ok(localVar4, "The localVar4 hasn't been created correctly."); + ok(localVar5, "The localVar5 hasn't been created correctly."); + ok(localVar6, "The localVar6 hasn't been created correctly."); + ok(localVar7, "The localVar7 hasn't been created correctly."); + ok(localVar8, "The localVar8 hasn't been created correctly."); + ok(localVar9, "The localVar9 hasn't been created correctly."); + + localVar0.setGrip(42); + localVar1.setGrip(true); + localVar2.setGrip("nasu"); + + localVar3.setGrip({ type: "undefined" }); + localVar4.setGrip({ type: "null" }); + localVar5.setGrip({ type: "object", class: "Object" }); + localVar6.setGrip({ type: "Infinity" }); + localVar7.setGrip({ type: "-Infinity" }); + localVar8.setGrip({ type: "NaN" }); + localVar9.setGrip({ type: "-0" }); + + localVar5.addItems({ + someProp0: { value: 42, enumerable: true }, + someProp1: { value: true, enumerable: true }, + someProp2: { value: "nasu", enumerable: true }, + someProp3: { value: { type: "undefined" }, enumerable: true }, + someProp4: { value: { type: "null" }, enumerable: true }, + someProp5: { value: { type: "object", class: "Object" }, enumerable: true }, + someProp6: { value: { type: "Infinity" }, enumerable: true }, + someProp7: { value: { type: "-Infinity" }, enumerable: true }, + someProp8: { value: { type: "NaN" }, enumerable: true }, + someProp9: { value: { type: "-0" }, enumerable: true }, + someUndefined: { + get: { type: "undefined" }, + set: { type: "undefined" }, + enumerable: true + }, + someAccessor: { + get: { type: "object", class: "Function" }, + set: { type: "undefined" }, + enumerable: true + } + }); + + localVar5.get("someProp5").addItems({ + someProp0: { value: 42, enumerable: true }, + someProp1: { value: true, enumerable: true }, + someProp2: { value: "nasu", enumerable: true }, + someProp3: { value: { type: "undefined" }, enumerable: true }, + someProp4: { value: { type: "null" }, enumerable: true }, + someProp5: { value: { type: "object", class: "Object" }, enumerable: true }, + someProp6: { value: { type: "Infinity" }, enumerable: true }, + someProp7: { value: { type: "-Infinity" }, enumerable: true }, + someProp8: { value: { type: "NaN" }, enumerable: true }, + someProp9: { value: { type: "-0" }, enumerable: true }, + someUndefined: { + get: { type: "undefined" }, + set: { type: "undefined" }, + enumerable: true + }, + someAccessor: { + get: { type: "object", class: "Function" }, + set: { type: "undefined" }, + enumerable: true + } + }); + + is(globalScope.target.querySelector(".enum").childNodes.length, 0, + "The globalScope doesn't contain all the created enumerable variable elements."); + is(globalScope.target.querySelector(".nonenum").childNodes.length, 2, + "The globalScope doesn't contain all the created non-enumerable variable elements."); + + is(localScope.target.querySelector(".enum").childNodes.length, 0, + "The localScope doesn't contain all the created enumerable variable elements."); + is(localScope.target.querySelector(".nonenum").childNodes.length, 10, + "The localScope doesn't contain all the created non-enumerable variable elements."); + + is(localVar5.target.querySelector(".enum").childNodes.length, 12, + "The localVar5 doesn't contain all the created enumerable properties."); + is(localVar5.target.querySelector(".nonenum").childNodes.length, 0, + "The localVar5 doesn't contain all the created non-enumerable properties."); + + is(localVar5.get("someProp5").target.querySelector(".enum").childNodes.length, 12, + "The localVar5.someProp5 doesn't contain all the created enumerable properties."); + is(localVar5.get("someProp5").target.querySelector(".nonenum").childNodes.length, 0, + "The localVar5.someProp5 doesn't contain all the created non-enumerable properties."); + + is(windowVar.target.querySelector(".value").getAttribute("value"), "Window", + "The grip information for the windowVar wasn't set correctly."); + is(documentVar.target.querySelector(".value").getAttribute("value"), "HTMLDocument", + "The grip information for the documentVar wasn't set correctly."); + + is(localVar0.target.querySelector(".value").getAttribute("value"), "42", + "The grip information for the localVar0 wasn't set correctly."); + is(localVar1.target.querySelector(".value").getAttribute("value"), "true", + "The grip information for the localVar1 wasn't set correctly."); + is(localVar2.target.querySelector(".value").getAttribute("value"), "\"nasu\"", + "The grip information for the localVar2 wasn't set correctly."); + is(localVar3.target.querySelector(".value").getAttribute("value"), "undefined", + "The grip information for the localVar3 wasn't set correctly."); + is(localVar4.target.querySelector(".value").getAttribute("value"), "null", + "The grip information for the localVar4 wasn't set correctly."); + is(localVar5.target.querySelector(".value").getAttribute("value"), "Object", + "The grip information for the localVar5 wasn't set correctly."); + is(localVar6.target.querySelector(".value").getAttribute("value"), "Infinity", + "The grip information for the localVar6 wasn't set correctly."); + is(localVar7.target.querySelector(".value").getAttribute("value"), "-Infinity", + "The grip information for the localVar7 wasn't set correctly."); + is(localVar8.target.querySelector(".value").getAttribute("value"), "NaN", + "The grip information for the localVar8 wasn't set correctly."); + is(localVar9.target.querySelector(".value").getAttribute("value"), "-0", + "The grip information for the localVar9 wasn't set correctly."); + + is(localVar5.get("someProp0").target.querySelector(".value").getAttribute("value"), "42", + "The grip information for the someProp0 wasn't set correctly."); + is(localVar5.get("someProp1").target.querySelector(".value").getAttribute("value"), "true", + "The grip information for the someProp1 wasn't set correctly."); + is(localVar5.get("someProp2").target.querySelector(".value").getAttribute("value"), "\"nasu\"", + "The grip information for the someProp2 wasn't set correctly."); + is(localVar5.get("someProp3").target.querySelector(".value").getAttribute("value"), "undefined", + "The grip information for the someProp3 wasn't set correctly."); + is(localVar5.get("someProp4").target.querySelector(".value").getAttribute("value"), "null", + "The grip information for the someProp4 wasn't set correctly."); + is(localVar5.get("someProp5").target.querySelector(".value").getAttribute("value"), "Object", + "The grip information for the someProp5 wasn't set correctly."); + is(localVar5.get("someProp6").target.querySelector(".value").getAttribute("value"), "Infinity", + "The grip information for the someProp6 wasn't set correctly."); + is(localVar5.get("someProp7").target.querySelector(".value").getAttribute("value"), "-Infinity", + "The grip information for the someProp7 wasn't set correctly."); + is(localVar5.get("someProp8").target.querySelector(".value").getAttribute("value"), "NaN", + "The grip information for the someProp8 wasn't set correctly."); + is(localVar5.get("someProp9").target.querySelector(".value").getAttribute("value"), "-0", + "The grip information for the someProp9 wasn't set correctly."); + is(localVar5.get("someUndefined").target.querySelector(".value").getAttribute("value"), "", + "The grip information for the someUndefined wasn't set correctly."); + is(localVar5.get("someAccessor").target.querySelector(".value").getAttribute("value"), "", + "The grip information for the someAccessor wasn't set correctly."); + + is(localVar5.get("someProp5").get("someProp0").target.querySelector(".value").getAttribute("value"), "42", + "The grip information for the sub-someProp0 wasn't set correctly."); + is(localVar5.get("someProp5").get("someProp1").target.querySelector(".value").getAttribute("value"), "true", + "The grip information for the sub-someProp1 wasn't set correctly."); + is(localVar5.get("someProp5").get("someProp2").target.querySelector(".value").getAttribute("value"), "\"nasu\"", + "The grip information for the sub-someProp2 wasn't set correctly."); + is(localVar5.get("someProp5").get("someProp3").target.querySelector(".value").getAttribute("value"), "undefined", + "The grip information for the sub-someProp3 wasn't set correctly."); + is(localVar5.get("someProp5").get("someProp4").target.querySelector(".value").getAttribute("value"), "null", + "The grip information for the sub-someProp4 wasn't set correctly."); + is(localVar5.get("someProp5").get("someProp5").target.querySelector(".value").getAttribute("value"), "Object", + "The grip information for the sub-someProp5 wasn't set correctly."); + is(localVar5.get("someProp5").get("someProp6").target.querySelector(".value").getAttribute("value"), "Infinity", + "The grip information for the sub-someProp6 wasn't set correctly."); + is(localVar5.get("someProp5").get("someProp7").target.querySelector(".value").getAttribute("value"), "-Infinity", + "The grip information for the sub-someProp7 wasn't set correctly."); + is(localVar5.get("someProp5").get("someProp8").target.querySelector(".value").getAttribute("value"), "NaN", + "The grip information for the sub-someProp8 wasn't set correctly."); + is(localVar5.get("someProp5").get("someProp9").target.querySelector(".value").getAttribute("value"), "-0", + "The grip information for the sub-someProp9 wasn't set correctly."); + is(localVar5.get("someProp5").get("someUndefined").target.querySelector(".value").getAttribute("value"), "", + "The grip information for the sub-someUndefined wasn't set correctly."); + is(localVar5.get("someProp5").get("someAccessor").target.querySelector(".value").getAttribute("value"), "", + "The grip information for the sub-someAccessor wasn't set correctly."); + + closeDebuggerAndFinish(aPanel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-06.js new file mode 100644 index 000000000..6d923eb02 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-06.js @@ -0,0 +1,125 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that Promises get their internal state added as psuedo properties. + */ + +const TAB_URL = EXAMPLE_URL + "doc_promise.html"; + +var test = Task.async(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + const [tab,, panel] = yield initDebugger(TAB_URL, options); + + const scopes = waitForCaretAndScopes(panel, 21); + callInTab(tab, "doPause"); + yield scopes; + + const variables = panel.panelWin.DebuggerView.Variables; + ok(variables, "Should get the variables view."); + + const scope = [...variables][0]; + ok(scope, "Should get the current function's scope."); + + const promiseVariables = [...scope].filter(([name]) => + ["p", "f", "r"].indexOf(name) !== -1); + + is(promiseVariables.length, 3, + "Should have our 3 promise variables: p, f, r"); + + for (let [name, item] of promiseVariables) { + info("Expanding variable '" + name + "'"); + let expanded = once(variables, "fetched"); + item.expand(); + yield expanded; + + let foundState = false; + switch (name) { + case "p": + for (let [property, { value }] of item) { + if (property !== "<state>") { + isnot(property, "<value>", + "A pending promise shouldn't have a value"); + isnot(property, "<reason>", + "A pending promise shouldn't have a reason"); + continue; + } + + foundState = true; + is(value, "pending", "The state should be pending."); + } + ok(foundState, "We should have found the <state> property."); + break; + + case "f": + let foundValue = false; + for (let [property, value] of item) { + if (property === "<state>") { + foundState = true; + is(value.value, "fulfilled", "The state should be fulfilled."); + } else if (property === "<value>") { + foundValue = true; + + let expanded = once(variables, "fetched"); + value.expand(); + yield expanded; + + let expectedProps = new Map([["a", 1], ["b", 2], ["c", 3]]); + for (let [prop, val] of value) { + if (prop === "__proto__") { + continue; + } + ok(expectedProps.has(prop), "The property should be expected."); + is(val.value, expectedProps.get(prop), "The property value should be correct."); + expectedProps.delete(prop); + } + is(Object.keys(expectedProps).length, 0, + "Should have found all of the expected properties."); + } else { + isnot(property, "<reason>", + "A fulfilled promise shouldn't have a reason"); + } + } + ok(foundState, "We should have found the <state> property."); + ok(foundValue, "We should have found the <value> property."); + break; + + case "r": + let foundReason = false; + for (let [property, value] of item) { + if (property === "<state>") { + foundState = true; + is(value.value, "rejected", "The state should be rejected."); + } else if (property === "<reason>") { + foundReason = true; + + let expanded = once(variables, "fetched"); + value.expand(); + yield expanded; + + let foundMessage = false; + for (let [prop, val] of value) { + if (prop !== "message") { + continue; + } + foundMessage = true; + is(val.value, "uh oh", "Should have the correct error message."); + } + ok(foundMessage, "Should have found the error's message"); + } else { + isnot(property, "<value>", + "A rejected promise shouldn't have a value"); + } + } + ok(foundState, "We should have found the <state> property."); + break; + } + } + + resumeDebuggerThenCloseAndFinish(panel); +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-07.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-07.js new file mode 100644 index 000000000..a05f33e7f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-07.js @@ -0,0 +1,69 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that proxy objects get their internal state added as pseudo properties. + */ + +const TAB_URL = EXAMPLE_URL + "doc_proxy.html"; + +var test = Task.async(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + var dbg = initDebugger(TAB_URL, options); + const [tab,, panel] = yield dbg; + const debuggerLineNumber = 34; + const scopes = waitForCaretAndScopes(panel, debuggerLineNumber); + callInTab(tab, "doPause"); + yield scopes; + + const variables = panel.panelWin.DebuggerView.Variables; + ok(variables, "Should get the variables view."); + + const scope = [...variables][0]; + ok(scope, "Should get the current function's scope."); + + let proxy; + for (let [name, value] of scope) { + if (name === "proxy") { + proxy = value; + } + } + ok(proxy, "Should have found the proxy variable"); + + info("Expanding variable 'proxy'"); + let expanded = once(variables, "fetched"); + proxy.expand(); + yield expanded; + + let foundTarget = false; + let foundHandler = false; + for (let [property, data] of proxy) { + info("Expanding property '" + property + "'"); + let expanded = once(variables, "fetched"); + data.expand(); + yield expanded; + if (property === "<target>") { + for(let [subprop, subdata] of data) if(subprop === "name") { + is(subdata.value, "target", "The value of '<target>' should be the [[ProxyTarget]]"); + foundTarget = true; + } + } else { + is(property, "<handler>", "There shouldn't be properties other than <target> and <handler>"); + for (let [subprop, subdata] of data) { + if(subprop === "name") { + is(subdata.value, "handler", "The value of '<handler>' should be the [[ProxyHandler]]"); + foundHandler = true; + } + } + } + } + ok(foundTarget, "Should have found the '<target>' property containing the [[ProxyTarget]]"); + ok(foundHandler, "Should have found the '<handler>' property containing the [[ProxyHandler]]"); + + resumeDebuggerThenCloseAndFinish(panel); +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-08.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-08.js new file mode 100644 index 000000000..83083eef3 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-08.js @@ -0,0 +1,61 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that property values are not missing when the property names only contain whitespace. + */ + +const TAB_URL = EXAMPLE_URL + "doc_whitespace-property-names.html"; + +var test = Task.async(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + var dbg = initDebugger(TAB_URL, options); + const [tab,, panel] = yield dbg; + const debuggerLineNumber = 24; + const scopes = waitForCaretAndScopes(panel, debuggerLineNumber); + callInTab(tab, "doPause"); + yield scopes; + + const variables = panel.panelWin.DebuggerView.Variables; + ok(variables, "Should get the variables view."); + + const scope = [...variables][0]; + ok(scope, "Should get the current function's scope."); + + let obj; + for (let [name, value] of scope) { + if (name === "obj") { + obj = value; + } + } + ok(obj, "Should have found the 'obj' variable"); + + info("Expanding variable 'obj'"); + let expanded = once(variables, "fetched"); + obj.expand(); + yield expanded; + + let values = ["", " ", "\r", "\n", "\t", "\f", "\uFEFF", "\xA0"]; + let count = values.length; + + for (let [property, value] of obj) { + let index = values.indexOf(property); + if (index >= 0) { + --count; + is(value._nameString, property, + "The _nameString is different than the property name"); + is(value._valueString, index + "", + "The _valueString is different than the stringified value"); + is(value._valueLabel.getAttribute("value"), index + "", + "The _valueLabel value is different than the stringified value"); + } + } + is(count, 0, "There are " + count + " missing properties"); + + resumeDebuggerThenCloseAndFinish(panel); +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-accessibility.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-accessibility.js new file mode 100644 index 000000000..6acec5583 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-accessibility.js @@ -0,0 +1,557 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view is keyboard accessible. + */ + +var gTab, gPanel, gDebugger; +var gVariablesView; + +function test() { + initDebugger().then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVariablesView = gDebugger.DebuggerView.Variables; + + performTest().then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); +} + +function performTest() { + let arr = [ + 42, + true, + "nasu", + undefined, + null, + [0, 1, 2], + { prop1: 9, prop2: 8 } + ]; + + let obj = { + p0: 42, + p1: true, + p2: "nasu", + p3: undefined, + p4: null, + p5: [3, 4, 5], + p6: { prop1: 7, prop2: 6 }, + get p7() { return arr; }, + set p8(value) { arr[0] = value; } + }; + + let test = { + someProp0: 42, + someProp1: true, + someProp2: "nasu", + someProp3: undefined, + someProp4: null, + someProp5: arr, + someProp6: obj, + get someProp7() { return arr; }, + set someProp7(value) { arr[0] = value; } + }; + + gVariablesView.eval = function () {}; + gVariablesView.switch = function () {}; + gVariablesView.delete = function () {}; + gVariablesView.rawObject = test; + gVariablesView.scrollPageSize = 5; + + return Task.spawn(function* () { + yield waitForTick(); + + // Part 0: Test generic focus methods on the variables view. + + gVariablesView.focusFirstVisibleItem(); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + gVariablesView.focusNextItem(); + is(gVariablesView.getFocusedItem().name, "someProp1", + "The 'someProp1' item should be focused."); + + gVariablesView.focusPrevItem(); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + // Part 1: Make sure that UP/DOWN keys don't scroll the variables view. + + yield synthesizeKeyAndWaitForTick("VK_DOWN", {}); + is(gVariablesView._parent.scrollTop, 0, + "The 'variables' view shouldn't scroll when pressing the DOWN key."); + + yield synthesizeKeyAndWaitForTick("VK_UP", {}); + is(gVariablesView._parent.scrollTop, 0, + "The 'variables' view shouldn't scroll when pressing the UP key."); + + // Part 2: Make sure that RETURN/ESCAPE toggle input elements. + + yield synthesizeKeyAndWaitForElement("VK_RETURN", {}, ".element-value-input", true); + yield synthesizeKeyAndWaitForElement("VK_ESCAPE", {}, ".element-value-input", false); + yield synthesizeKeyAndWaitForElement("VK_RETURN", { shiftKey: true }, ".element-name-input", true); + yield synthesizeKeyAndWaitForElement("VK_ESCAPE", {}, ".element-name-input", false); + + // Part 3: Test simple navigation. + + EventUtils.sendKey("DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp1", + "The 'someProp1' item should be focused."); + + EventUtils.sendKey("UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + EventUtils.sendKey("PAGE_DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp5", + "The 'someProp5' item should be focused."); + + EventUtils.sendKey("PAGE_UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + EventUtils.sendKey("END", gDebugger); + is(gVariablesView.getFocusedItem().name, "__proto__", + "The '__proto__' item should be focused."); + + EventUtils.sendKey("HOME", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + // Part 4: Test if pressing the same navigation key twice works as expected. + + EventUtils.sendKey("DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp1", + "The 'someProp1' item should be focused."); + + EventUtils.sendKey("DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp2", + "The 'someProp2' item should be focused."); + + EventUtils.sendKey("UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp1", + "The 'someProp1' item should be focused."); + + EventUtils.sendKey("UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + EventUtils.sendKey("PAGE_DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp5", + "The 'someProp5' item should be focused."); + + EventUtils.sendKey("PAGE_DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "__proto__", + "The '__proto__' item should be focused."); + + EventUtils.sendKey("PAGE_UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp5", + "The 'someProp5' item should be focused."); + + EventUtils.sendKey("PAGE_UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + // Part 5: Test that HOME/PAGE_UP/PAGE_DOWN are symmetrical. + + EventUtils.sendKey("HOME", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + EventUtils.sendKey("HOME", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + EventUtils.sendKey("PAGE_UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + EventUtils.sendKey("HOME", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + EventUtils.sendKey("END", gDebugger); + is(gVariablesView.getFocusedItem().name, "__proto__", + "The '__proto__' item should be focused."); + + EventUtils.sendKey("END", gDebugger); + is(gVariablesView.getFocusedItem().name, "__proto__", + "The '__proto__' item should be focused."); + + EventUtils.sendKey("PAGE_DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "__proto__", + "The '__proto__' item should be focused."); + + EventUtils.sendKey("END", gDebugger); + is(gVariablesView.getFocusedItem().name, "__proto__", + "The '__proto__' item should be focused."); + + // Part 6: Test that focus doesn't leave the variables view. + + EventUtils.sendKey("PAGE_UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp5", + "The 'someProp5' item should be focused."); + + EventUtils.sendKey("PAGE_UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + EventUtils.sendKey("UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + EventUtils.sendKey("UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + EventUtils.sendKey("PAGE_DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp5", + "The 'someProp5' item should be focused."); + + EventUtils.sendKey("PAGE_DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "__proto__", + "The '__proto__' item should be focused."); + + EventUtils.sendKey("PAGE_DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "__proto__", + "The '__proto__' item should be focused."); + + EventUtils.sendKey("PAGE_DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "__proto__", + "The '__proto__' item should be focused."); + + // Part 7: Test that random offsets don't occur in tandem with HOME/END. + + EventUtils.sendKey("HOME", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + EventUtils.sendKey("DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp1", + "The 'someProp1' item should be focused."); + + EventUtils.sendKey("END", gDebugger); + is(gVariablesView.getFocusedItem().name, "__proto__", + "The '__proto__' item should be focused."); + + EventUtils.sendKey("DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "__proto__", + "The '__proto__' item should be focused."); + + // Part 8: Test that the RIGHT key expands elements as intended. + + EventUtils.sendKey("PAGE_UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp5", + "The 'someProp5' item should be focused."); + is(gVariablesView.getFocusedItem().expanded, false, + "The 'someProp5' item should not be expanded yet."); + + yield synthesizeKeyAndWaitForTick("VK_RIGHT", {}); + is(gVariablesView.getFocusedItem().name, "someProp5", + "The 'someProp5' item should be focused."); + is(gVariablesView.getFocusedItem().expanded, true, + "The 'someProp5' item should now be expanded."); + is(gVariablesView.getFocusedItem()._store.size, 9, + "There should be 9 properties in the selected variable."); + is(gVariablesView.getFocusedItem()._enumItems.length, 7, + "There should be 7 enumerable properties in the selected variable."); + is(gVariablesView.getFocusedItem()._nonEnumItems.length, 2, + "There should be 2 non-enumerable properties in the selected variable."); + + yield waitForChildNodes(gVariablesView.getFocusedItem()._enum, 7); + yield waitForChildNodes(gVariablesView.getFocusedItem()._nonenum, 2); + + EventUtils.sendKey("RIGHT", gDebugger); + is(gVariablesView.getFocusedItem().name, "0", + "The '0' item should be focused."); + + EventUtils.sendKey("RIGHT", gDebugger); + is(gVariablesView.getFocusedItem().name, "0", + "The '0' item should still be focused."); + + EventUtils.sendKey("PAGE_DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "5", + "The '5' item should be focused."); + is(gVariablesView.getFocusedItem().expanded, false, + "The '5' item should not be expanded yet."); + + yield synthesizeKeyAndWaitForTick("VK_RIGHT", {}); + is(gVariablesView.getFocusedItem().name, "5", + "The '5' item should be focused."); + is(gVariablesView.getFocusedItem().expanded, true, + "The '5' item should now be expanded."); + is(gVariablesView.getFocusedItem()._store.size, 5, + "There should be 5 properties in the selected variable."); + is(gVariablesView.getFocusedItem()._enumItems.length, 3, + "There should be 3 enumerable properties in the selected variable."); + is(gVariablesView.getFocusedItem()._nonEnumItems.length, 2, + "There should be 2 non-enumerable properties in the selected variable."); + + yield waitForChildNodes(gVariablesView.getFocusedItem()._enum, 3); + yield waitForChildNodes(gVariablesView.getFocusedItem()._nonenum, 2); + + EventUtils.sendKey("RIGHT", gDebugger); + is(gVariablesView.getFocusedItem().name, "0", + "The '0' item should be focused."); + + EventUtils.sendKey("RIGHT", gDebugger); + is(gVariablesView.getFocusedItem().name, "0", + "The '0' item should still be focused."); + + EventUtils.sendKey("PAGE_DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "6", + "The '6' item should be focused."); + is(gVariablesView.getFocusedItem().expanded, false, + "The '6' item should not be expanded yet."); + + yield synthesizeKeyAndWaitForTick("VK_RIGHT", {}); + is(gVariablesView.getFocusedItem().name, "6", + "The '6' item should be focused."); + is(gVariablesView.getFocusedItem().expanded, true, + "The '6' item should now be expanded."); + is(gVariablesView.getFocusedItem()._store.size, 3, + "There should be 3 properties in the selected variable."); + is(gVariablesView.getFocusedItem()._enumItems.length, 2, + "There should be 2 enumerable properties in the selected variable."); + is(gVariablesView.getFocusedItem()._nonEnumItems.length, 1, + "There should be 1 non-enumerable properties in the selected variable."); + + yield waitForChildNodes(gVariablesView.getFocusedItem()._enum, 2); + yield waitForChildNodes(gVariablesView.getFocusedItem()._nonenum, 1); + + EventUtils.sendKey("RIGHT", gDebugger); + is(gVariablesView.getFocusedItem().name, "prop1", + "The 'prop1' item should be focused."); + + EventUtils.sendKey("RIGHT", gDebugger); + is(gVariablesView.getFocusedItem().name, "prop1", + "The 'prop1' item should still be focused."); + + EventUtils.sendKey("PAGE_DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp6", + "The 'someProp6' item should be focused."); + is(gVariablesView.getFocusedItem().expanded, false, + "The 'someProp6' item should not be expanded yet."); + + // Part 9: Test that the RIGHT key collapses elements as intended. + + EventUtils.sendKey("LEFT", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp6", + "The 'someProp6' item should be focused."); + + EventUtils.sendKey("UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "__proto__", + "The '__proto__' item should be focused."); + + EventUtils.sendKey("LEFT", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp5", + "The 'someProp5' item should be focused."); + is(gVariablesView.getFocusedItem().expanded, true, + "The '6' item should still be expanded."); + + EventUtils.sendKey("LEFT", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp5", + "The 'someProp5' item should still be focused."); + is(gVariablesView.getFocusedItem().expanded, false, + "The '6' item should still not be expanded anymore."); + + EventUtils.sendKey("LEFT", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp5", + "The 'someProp5' item should still be focused."); + + // Part 9: Test continuous navigation. + + EventUtils.sendKey("UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp4", + "The 'someProp4' item should be focused."); + + EventUtils.sendKey("UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp3", + "The 'someProp3' item should be focused."); + + EventUtils.sendKey("UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp2", + "The 'someProp2' item should be focused."); + + EventUtils.sendKey("UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp1", + "The 'someProp1' item should be focused."); + + EventUtils.sendKey("UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + EventUtils.sendKey("PAGE_UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp0", + "The 'someProp0' item should be focused."); + + EventUtils.sendKey("PAGE_DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp5", + "The 'someProp5' item should be focused."); + + EventUtils.sendKey("DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp6", + "The 'someProp6' item should be focused."); + + EventUtils.sendKey("DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp7", + "The 'someProp7' item should be focused."); + + EventUtils.sendKey("DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "get", + "The 'get' item should be focused."); + + EventUtils.sendKey("DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "set", + "The 'set' item should be focused."); + + EventUtils.sendKey("DOWN", gDebugger); + is(gVariablesView.getFocusedItem().name, "__proto__", + "The '__proto__' item should be focused."); + + // Part 10: Test that BACKSPACE deletes items in the variables view. + + EventUtils.sendKey("BACK_SPACE", gDebugger); + is(gVariablesView.getFocusedItem().name, "__proto__", + "The '__proto__' variable should still be focused."); + is(gVariablesView.getFocusedItem().value, "[object Object]", + "The '__proto__' variable should not have an empty value."); + is(gVariablesView.getFocusedItem().visible, false, + "The '__proto__' variable should be hidden."); + + EventUtils.sendKey("UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "set", + "The 'set' item should be focused."); + is(gVariablesView.getFocusedItem().value, "[object Object]", + "The 'set' item should not have an empty value."); + is(gVariablesView.getFocusedItem().visible, true, + "The 'set' item should be visible."); + + EventUtils.sendKey("BACK_SPACE", gDebugger); + is(gVariablesView.getFocusedItem().name, "set", + "The 'set' item should still be focused."); + is(gVariablesView.getFocusedItem().value, "[object Object]", + "The 'set' item should not have an empty value."); + is(gVariablesView.getFocusedItem().visible, true, + "The 'set' item should be visible."); + is(gVariablesView.getFocusedItem().twisty, false, + "The 'set' item should be disabled and have a hidden twisty."); + + EventUtils.sendKey("UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "get", + "The 'get' item should be focused."); + is(gVariablesView.getFocusedItem().value, "[object Object]", + "The 'get' item should not have an empty value."); + is(gVariablesView.getFocusedItem().visible, true, + "The 'get' item should be visible."); + + EventUtils.sendKey("BACK_SPACE", gDebugger); + is(gVariablesView.getFocusedItem().name, "get", + "The 'get' item should still be focused."); + is(gVariablesView.getFocusedItem().value, "[object Object]", + "The 'get' item should not have an empty value."); + is(gVariablesView.getFocusedItem().visible, true, + "The 'get' item should be visible."); + is(gVariablesView.getFocusedItem().twisty, false, + "The 'get' item should be disabled and have a hidden twisty."); + + EventUtils.sendKey("UP", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp7", + "The 'someProp7' item should be focused."); + is(gVariablesView.getFocusedItem().value, undefined, + "The 'someProp7' variable should have an empty value."); + is(gVariablesView.getFocusedItem().visible, true, + "The 'someProp7' variable should be visible."); + + EventUtils.sendKey("BACK_SPACE", gDebugger); + is(gVariablesView.getFocusedItem().name, "someProp7", + "The 'someProp7' variable should still be focused."); + is(gVariablesView.getFocusedItem().value, undefined, + "The 'someProp7' variable should have an empty value."); + is(gVariablesView.getFocusedItem().visible, false, + "The 'someProp7' variable should be hidden."); + + // Part 11: Test that Ctrl-C copies the current item to the system clipboard + + gVariablesView.focusFirstVisibleItem(); + let copied = promise.defer(); + let expectedValue = gVariablesView.getFocusedItem().name + + gVariablesView.getFocusedItem().separatorStr + + gVariablesView.getFocusedItem().value; + + waitForClipboard(expectedValue, function setup() { + EventUtils.synthesizeKey("C", { metaKey: true }, gDebugger); + }, copied.resolve, copied.reject + ); + + try { + yield copied.promise; + ok(true, + "Ctrl-C copied the selected item to the clipboard."); + } catch (e) { + ok(false, + "Ctrl-C didn't copy the selected item to the clipboard."); + } + + yield closeDebuggerAndFinish(gPanel); + }); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVariablesView = null; +}); + +function synthesizeKeyAndWaitForElement(aKey, aModifiers, aSelector, aExistence) { + EventUtils.synthesizeKey(aKey, aModifiers, gDebugger); + return waitForElement(aSelector, aExistence); +} + +function synthesizeKeyAndWaitForTick(aKey, aModifiers) { + EventUtils.synthesizeKey(aKey, aModifiers, gDebugger); + return waitForTick(); +} + +function waitForElement(aSelector, aExistence) { + return waitForPredicate(() => { + return !!gVariablesView._list.querySelector(aSelector) == aExistence; + }); +} + +function waitForChildNodes(aTarget, aCount) { + return waitForPredicate(() => { + return aTarget.childNodes.length == aCount; + }); +} + +function waitForPredicate(aPredicate, aInterval = 10) { + let deferred = promise.defer(); + + // Poll every few milliseconds until the element is retrieved. + let count = 0; + let intervalID = window.setInterval(() => { + // Make sure we don't wait for too long. + if (++count > 1000) { + deferred.reject("Timed out while polling for the element."); + window.clearInterval(intervalID); + return; + } + // Check if the predicate condition is fulfilled. + if (!aPredicate()) { + return; + } + // We got the element, it's safe to callback. + window.clearInterval(intervalID); + deferred.resolve(); + }, aInterval); + + return deferred.promise; +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-data.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-data.js new file mode 100644 index 000000000..02679e073 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-data.js @@ -0,0 +1,611 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view correctly populates itself + * when given some raw data. + */ + +var gTab, gPanel, gDebugger; +var gVariablesView, gScope, gVariable; + +function test() { + initDebugger().then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVariablesView = gDebugger.DebuggerView.Variables; + + performTest(); + }); +} + +function performTest() { + let arr = [ + 42, + true, + "nasu", + undefined, + null, + [0, 1, 2], + { prop1: 9, prop2: 8 } + ]; + + let obj = { + p0: 42, + p1: true, + p2: "nasu", + p3: undefined, + p4: null, + p5: [3, 4, 5], + p6: { prop1: 7, prop2: 6 }, + get p7() { return arr; }, + set p8(value) { arr[0] = value; } + }; + + let test = { + someProp0: 42, + someProp1: true, + someProp2: "nasu", + someProp3: undefined, + someProp4: null, + someProp5: arr, + someProp6: obj, + get someProp7() { return arr; }, + set someProp7(value) { arr[0] = value; } + }; + + gVariablesView.eval = function () {}; + gVariablesView.switch = function () {}; + gVariablesView.delete = function () {}; + gVariablesView.new = function () {}; + gVariablesView.rawObject = test; + + testHierarchy(); + testHeader(); + testFirstLevelContents(); + testSecondLevelContents(); + testThirdLevelContents(); + testOriginalRawDataIntegrity(arr, obj); + + let fooScope = gVariablesView.addScope("foo"); + let anonymousVar = fooScope.addItem(); + + let anonymousScope = gVariablesView.addScope(); + let barVar = anonymousScope.addItem("bar"); + let bazProperty = barVar.addItem("baz"); + + testAnonymousHeaders(fooScope, anonymousVar, anonymousScope, barVar, bazProperty); + testPropertyInheritance(fooScope, anonymousVar, anonymousScope, barVar, bazProperty); + + testClearHierarchy(); + closeDebuggerAndFinish(gPanel); +} + +function testHierarchy() { + is(gVariablesView._currHierarchy.size, 13, + "There should be 1 scope, 1 var, 1 proto, 8 props, 1 getter and 1 setter."); + + gScope = gVariablesView._currHierarchy.get(""); + gVariable = gVariablesView._currHierarchy.get("[]"); + + is(gVariablesView._store.length, 1, + "There should be only one scope in the view."); + is(gScope._store.size, 1, + "There should be only one variable in the scope."); + is(gVariable._store.size, 9, + "There should be 1 __proto__ and 8 properties in the variable."); +} + +function testHeader() { + is(gScope.header, false, + "The scope title header should be hidden."); + is(gVariable.header, false, + "The variable title header should be hidden."); + + gScope.showHeader(); + gVariable.showHeader(); + + is(gScope.header, false, + "The scope title header should still not be visible."); + is(gVariable.header, false, + "The variable title header should still not be visible."); + + gScope.hideHeader(); + gVariable.hideHeader(); + + is(gScope.header, false, + "The scope title header should now still be hidden."); + is(gVariable.header, false, + "The variable title header should now still be hidden."); +} + +function testFirstLevelContents() { + let someProp0 = gVariable.get("someProp0"); + let someProp1 = gVariable.get("someProp1"); + let someProp2 = gVariable.get("someProp2"); + let someProp3 = gVariable.get("someProp3"); + let someProp4 = gVariable.get("someProp4"); + let someProp5 = gVariable.get("someProp5"); + let someProp6 = gVariable.get("someProp6"); + let someProp7 = gVariable.get("someProp7"); + let __proto__ = gVariable.get("__proto__"); + + is(someProp0.visible, true, "The first property visible state is correct."); + is(someProp1.visible, true, "The second property visible state is correct."); + is(someProp2.visible, true, "The third property visible state is correct."); + is(someProp3.visible, true, "The fourth property visible state is correct."); + is(someProp4.visible, true, "The fifth property visible state is correct."); + is(someProp5.visible, true, "The sixth property visible state is correct."); + is(someProp6.visible, true, "The seventh property visible state is correct."); + is(someProp7.visible, true, "The eight property visible state is correct."); + is(__proto__.visible, true, "The __proto__ property visible state is correct."); + + is(someProp0.expanded, false, "The first property expanded state is correct."); + is(someProp1.expanded, false, "The second property expanded state is correct."); + is(someProp2.expanded, false, "The third property expanded state is correct."); + is(someProp3.expanded, false, "The fourth property expanded state is correct."); + is(someProp4.expanded, false, "The fifth property expanded state is correct."); + is(someProp5.expanded, false, "The sixth property expanded state is correct."); + is(someProp6.expanded, false, "The seventh property expanded state is correct."); + is(someProp7.expanded, true, "The eight property expanded state is correct."); + is(__proto__.expanded, false, "The __proto__ property expanded state is correct."); + + is(someProp0.header, true, "The first property header state is correct."); + is(someProp1.header, true, "The second property header state is correct."); + is(someProp2.header, true, "The third property header state is correct."); + is(someProp3.header, true, "The fourth property header state is correct."); + is(someProp4.header, true, "The fifth property header state is correct."); + is(someProp5.header, true, "The sixth property header state is correct."); + is(someProp6.header, true, "The seventh property header state is correct."); + is(someProp7.header, true, "The eight property header state is correct."); + is(__proto__.header, true, "The __proto__ property header state is correct."); + + is(someProp0.twisty, false, "The first property twisty state is correct."); + is(someProp1.twisty, false, "The second property twisty state is correct."); + is(someProp2.twisty, false, "The third property twisty state is correct."); + is(someProp3.twisty, false, "The fourth property twisty state is correct."); + is(someProp4.twisty, false, "The fifth property twisty state is correct."); + is(someProp5.twisty, true, "The sixth property twisty state is correct."); + is(someProp6.twisty, true, "The seventh property twisty state is correct."); + is(someProp7.twisty, true, "The eight property twisty state is correct."); + is(__proto__.twisty, true, "The __proto__ property twisty state is correct."); + + is(someProp0.name, "someProp0", "The first property name is correct."); + is(someProp1.name, "someProp1", "The second property name is correct."); + is(someProp2.name, "someProp2", "The third property name is correct."); + is(someProp3.name, "someProp3", "The fourth property name is correct."); + is(someProp4.name, "someProp4", "The fifth property name is correct."); + is(someProp5.name, "someProp5", "The sixth property name is correct."); + is(someProp6.name, "someProp6", "The seventh property name is correct."); + is(someProp7.name, "someProp7", "The eight property name is correct."); + is(__proto__.name, "__proto__", "The __proto__ property name is correct."); + + is(someProp0.value, 42, "The first property value is correct."); + is(someProp1.value, true, "The second property value is correct."); + is(someProp2.value, "nasu", "The third property value is correct."); + is(someProp3.value.type, "undefined", "The fourth property value is correct."); + is(someProp4.value.type, "null", "The fifth property value is correct."); + is(someProp5.value.type, "object", "The sixth property value type is correct."); + is(someProp5.value.class, "Array", "The sixth property value class is correct."); + is(someProp6.value.type, "object", "The seventh property value type is correct."); + is(someProp6.value.class, "Object", "The seventh property value class is correct."); + is(someProp7.value, null, "The eight property value is correct."); + isnot(someProp7.getter, null, "The eight property getter is correct."); + isnot(someProp7.setter, null, "The eight property setter is correct."); + is(someProp7.getter.type, "object", "The eight property getter type is correct."); + is(someProp7.getter.class, "Function", "The eight property getter class is correct."); + is(someProp7.setter.type, "object", "The eight property setter type is correct."); + is(someProp7.setter.class, "Function", "The eight property setter class is correct."); + is(__proto__.value.type, "object", "The __proto__ property value type is correct."); + is(__proto__.value.class, "Object", "The __proto__ property value class is correct."); + + someProp0.expand(); + someProp1.expand(); + someProp2.expand(); + someProp3.expand(); + someProp4.expand(); + someProp7.expand(); + + ok(!someProp0.get("__proto__"), "Number primitives should not have a prototype"); + ok(!someProp1.get("__proto__"), "Boolean primitives should not have a prototype"); + ok(!someProp2.get("__proto__"), "String literals should not have a prototype"); + ok(!someProp3.get("__proto__"), "Undefined values should not have a prototype"); + ok(!someProp4.get("__proto__"), "Null values should not have a prototype"); + ok(!someProp7.get("__proto__"), "Getter properties should not have a prototype"); +} + +function testSecondLevelContents() { + let someProp5 = gVariable.get("someProp5"); + let someProp6 = gVariable.get("someProp6"); + + is(someProp5._store.size, 0, "No properties should be in someProp5 before expanding"); + someProp5.expand(); + is(someProp5._store.size, 9, "Some properties should be in someProp5 before expanding"); + + let arrayItem0 = someProp5.get("0"); + let arrayItem1 = someProp5.get("1"); + let arrayItem2 = someProp5.get("2"); + let arrayItem3 = someProp5.get("3"); + let arrayItem4 = someProp5.get("4"); + let arrayItem5 = someProp5.get("5"); + let arrayItem6 = someProp5.get("6"); + let __proto__ = someProp5.get("__proto__"); + + is(arrayItem0.visible, true, "The first array item visible state is correct."); + is(arrayItem1.visible, true, "The second array item visible state is correct."); + is(arrayItem2.visible, true, "The third array item visible state is correct."); + is(arrayItem3.visible, true, "The fourth array item visible state is correct."); + is(arrayItem4.visible, true, "The fifth array item visible state is correct."); + is(arrayItem5.visible, true, "The sixth array item visible state is correct."); + is(arrayItem6.visible, true, "The seventh array item visible state is correct."); + is(__proto__.visible, true, "The __proto__ property visible state is correct."); + + is(arrayItem0.expanded, false, "The first array item expanded state is correct."); + is(arrayItem1.expanded, false, "The second array item expanded state is correct."); + is(arrayItem2.expanded, false, "The third array item expanded state is correct."); + is(arrayItem3.expanded, false, "The fourth array item expanded state is correct."); + is(arrayItem4.expanded, false, "The fifth array item expanded state is correct."); + is(arrayItem5.expanded, false, "The sixth array item expanded state is correct."); + is(arrayItem6.expanded, false, "The seventh array item expanded state is correct."); + is(__proto__.expanded, false, "The __proto__ property expanded state is correct."); + + is(arrayItem0.header, true, "The first array item header state is correct."); + is(arrayItem1.header, true, "The second array item header state is correct."); + is(arrayItem2.header, true, "The third array item header state is correct."); + is(arrayItem3.header, true, "The fourth array item header state is correct."); + is(arrayItem4.header, true, "The fifth array item header state is correct."); + is(arrayItem5.header, true, "The sixth array item header state is correct."); + is(arrayItem6.header, true, "The seventh array item header state is correct."); + is(__proto__.header, true, "The __proto__ property header state is correct."); + + is(arrayItem0.twisty, false, "The first array item twisty state is correct."); + is(arrayItem1.twisty, false, "The second array item twisty state is correct."); + is(arrayItem2.twisty, false, "The third array item twisty state is correct."); + is(arrayItem3.twisty, false, "The fourth array item twisty state is correct."); + is(arrayItem4.twisty, false, "The fifth array item twisty state is correct."); + is(arrayItem5.twisty, true, "The sixth array item twisty state is correct."); + is(arrayItem6.twisty, true, "The seventh array item twisty state is correct."); + is(__proto__.twisty, true, "The __proto__ property twisty state is correct."); + + is(arrayItem0.name, "0", "The first array item name is correct."); + is(arrayItem1.name, "1", "The second array item name is correct."); + is(arrayItem2.name, "2", "The third array item name is correct."); + is(arrayItem3.name, "3", "The fourth array item name is correct."); + is(arrayItem4.name, "4", "The fifth array item name is correct."); + is(arrayItem5.name, "5", "The sixth array item name is correct."); + is(arrayItem6.name, "6", "The seventh array item name is correct."); + is(__proto__.name, "__proto__", "The __proto__ property name is correct."); + + is(arrayItem0.value, 42, "The first array item value is correct."); + is(arrayItem1.value, true, "The second array item value is correct."); + is(arrayItem2.value, "nasu", "The third array item value is correct."); + is(arrayItem3.value.type, "undefined", "The fourth array item value is correct."); + is(arrayItem4.value.type, "null", "The fifth array item value is correct."); + is(arrayItem5.value.type, "object", "The sixth array item value type is correct."); + is(arrayItem5.value.class, "Array", "The sixth array item value class is correct."); + is(arrayItem6.value.type, "object", "The seventh array item value type is correct."); + is(arrayItem6.value.class, "Object", "The seventh array item value class is correct."); + is(__proto__.value.type, "object", "The __proto__ property value type is correct."); + is(__proto__.value.class, "Array", "The __proto__ property value class is correct."); + + is(someProp6._store.size, 0, "No properties should be in someProp6 before expanding"); + someProp6.expand(); + is(someProp6._store.size, 10, "Some properties should be in someProp6 before expanding"); + + let objectItem0 = someProp6.get("p0"); + let objectItem1 = someProp6.get("p1"); + let objectItem2 = someProp6.get("p2"); + let objectItem3 = someProp6.get("p3"); + let objectItem4 = someProp6.get("p4"); + let objectItem5 = someProp6.get("p5"); + let objectItem6 = someProp6.get("p6"); + let objectItem7 = someProp6.get("p7"); + let objectItem8 = someProp6.get("p8"); + __proto__ = someProp6.get("__proto__"); + + is(objectItem0.visible, true, "The first object item visible state is correct."); + is(objectItem1.visible, true, "The second object item visible state is correct."); + is(objectItem2.visible, true, "The third object item visible state is correct."); + is(objectItem3.visible, true, "The fourth object item visible state is correct."); + is(objectItem4.visible, true, "The fifth object item visible state is correct."); + is(objectItem5.visible, true, "The sixth object item visible state is correct."); + is(objectItem6.visible, true, "The seventh object item visible state is correct."); + is(objectItem7.visible, true, "The eight object item visible state is correct."); + is(objectItem8.visible, true, "The ninth object item visible state is correct."); + is(__proto__.visible, true, "The __proto__ property visible state is correct."); + + is(objectItem0.expanded, false, "The first object item expanded state is correct."); + is(objectItem1.expanded, false, "The second object item expanded state is correct."); + is(objectItem2.expanded, false, "The third object item expanded state is correct."); + is(objectItem3.expanded, false, "The fourth object item expanded state is correct."); + is(objectItem4.expanded, false, "The fifth object item expanded state is correct."); + is(objectItem5.expanded, false, "The sixth object item expanded state is correct."); + is(objectItem6.expanded, false, "The seventh object item expanded state is correct."); + is(objectItem7.expanded, true, "The eight object item expanded state is correct."); + is(objectItem8.expanded, true, "The ninth object item expanded state is correct."); + is(__proto__.expanded, false, "The __proto__ property expanded state is correct."); + + is(objectItem0.header, true, "The first object item header state is correct."); + is(objectItem1.header, true, "The second object item header state is correct."); + is(objectItem2.header, true, "The third object item header state is correct."); + is(objectItem3.header, true, "The fourth object item header state is correct."); + is(objectItem4.header, true, "The fifth object item header state is correct."); + is(objectItem5.header, true, "The sixth object item header state is correct."); + is(objectItem6.header, true, "The seventh object item header state is correct."); + is(objectItem7.header, true, "The eight object item header state is correct."); + is(objectItem8.header, true, "The ninth object item header state is correct."); + is(__proto__.header, true, "The __proto__ property header state is correct."); + + is(objectItem0.twisty, false, "The first object item twisty state is correct."); + is(objectItem1.twisty, false, "The second object item twisty state is correct."); + is(objectItem2.twisty, false, "The third object item twisty state is correct."); + is(objectItem3.twisty, false, "The fourth object item twisty state is correct."); + is(objectItem4.twisty, false, "The fifth object item twisty state is correct."); + is(objectItem5.twisty, true, "The sixth object item twisty state is correct."); + is(objectItem6.twisty, true, "The seventh object item twisty state is correct."); + is(objectItem7.twisty, true, "The eight object item twisty state is correct."); + is(objectItem8.twisty, true, "The ninth object item twisty state is correct."); + is(__proto__.twisty, true, "The __proto__ property twisty state is correct."); + + is(objectItem0.name, "p0", "The first object item name is correct."); + is(objectItem1.name, "p1", "The second object item name is correct."); + is(objectItem2.name, "p2", "The third object item name is correct."); + is(objectItem3.name, "p3", "The fourth object item name is correct."); + is(objectItem4.name, "p4", "The fifth object item name is correct."); + is(objectItem5.name, "p5", "The sixth object item name is correct."); + is(objectItem6.name, "p6", "The seventh object item name is correct."); + is(objectItem7.name, "p7", "The eight seventh object item name is correct."); + is(objectItem8.name, "p8", "The ninth seventh object item name is correct."); + is(__proto__.name, "__proto__", "The __proto__ property name is correct."); + + is(objectItem0.value, 42, "The first object item value is correct."); + is(objectItem1.value, true, "The second object item value is correct."); + is(objectItem2.value, "nasu", "The third object item value is correct."); + is(objectItem3.value.type, "undefined", "The fourth object item value is correct."); + is(objectItem4.value.type, "null", "The fifth object item value is correct."); + is(objectItem5.value.type, "object", "The sixth object item value type is correct."); + is(objectItem5.value.class, "Array", "The sixth object item value class is correct."); + is(objectItem6.value.type, "object", "The seventh object item value type is correct."); + is(objectItem6.value.class, "Object", "The seventh object item value class is correct."); + is(objectItem7.value, null, "The eight object item value is correct."); + isnot(objectItem7.getter, null, "The eight object item getter is correct."); + isnot(objectItem7.setter, null, "The eight object item setter is correct."); + is(objectItem7.setter.type, "undefined", "The eight object item setter type is correct."); + is(objectItem7.getter.type, "object", "The eight object item getter type is correct."); + is(objectItem7.getter.class, "Function", "The eight object item getter class is correct."); + is(objectItem8.value, null, "The ninth object item value is correct."); + isnot(objectItem8.getter, null, "The ninth object item getter is correct."); + isnot(objectItem8.setter, null, "The ninth object item setter is correct."); + is(objectItem8.getter.type, "undefined", "The eight object item getter type is correct."); + is(objectItem8.setter.type, "object", "The ninth object item setter type is correct."); + is(objectItem8.setter.class, "Function", "The ninth object item setter class is correct."); + is(__proto__.value.type, "object", "The __proto__ property value type is correct."); + is(__proto__.value.class, "Object", "The __proto__ property value class is correct."); +} + +function testThirdLevelContents() { + (function () { + let someProp5 = gVariable.get("someProp5"); + let arrayItem5 = someProp5.get("5"); + let arrayItem6 = someProp5.get("6"); + + is(arrayItem5._store.size, 0, "No properties should be in arrayItem5 before expanding"); + arrayItem5.expand(); + is(arrayItem5._store.size, 5, "Some properties should be in arrayItem5 before expanding"); + + is(arrayItem6._store.size, 0, "No properties should be in arrayItem6 before expanding"); + arrayItem6.expand(); + is(arrayItem6._store.size, 3, "Some properties should be in arrayItem6 before expanding"); + + let arraySubItem0 = arrayItem5.get("0"); + let arraySubItem1 = arrayItem5.get("1"); + let arraySubItem2 = arrayItem5.get("2"); + let objectSubItem0 = arrayItem6.get("prop1"); + let objectSubItem1 = arrayItem6.get("prop2"); + + is(arraySubItem0.value, 0, "The first array sub-item value is correct."); + is(arraySubItem1.value, 1, "The second array sub-item value is correct."); + is(arraySubItem2.value, 2, "The third array sub-item value is correct."); + + is(objectSubItem0.value, 9, "The first object sub-item value is correct."); + is(objectSubItem1.value, 8, "The second object sub-item value is correct."); + + let array__proto__ = arrayItem5.get("__proto__"); + let object__proto__ = arrayItem6.get("__proto__"); + + ok(array__proto__, "The array should have a __proto__ property."); + ok(object__proto__, "The object should have a __proto__ property."); + })(); + + (function () { + let someProp6 = gVariable.get("someProp6"); + let objectItem5 = someProp6.get("p5"); + let objectItem6 = someProp6.get("p6"); + + is(objectItem5._store.size, 0, "No properties should be in objectItem5 before expanding"); + objectItem5.expand(); + is(objectItem5._store.size, 5, "Some properties should be in objectItem5 before expanding"); + + is(objectItem6._store.size, 0, "No properties should be in objectItem6 before expanding"); + objectItem6.expand(); + is(objectItem6._store.size, 3, "Some properties should be in objectItem6 before expanding"); + + let arraySubItem0 = objectItem5.get("0"); + let arraySubItem1 = objectItem5.get("1"); + let arraySubItem2 = objectItem5.get("2"); + let objectSubItem0 = objectItem6.get("prop1"); + let objectSubItem1 = objectItem6.get("prop2"); + + is(arraySubItem0.value, 3, "The first array sub-item value is correct."); + is(arraySubItem1.value, 4, "The second array sub-item value is correct."); + is(arraySubItem2.value, 5, "The third array sub-item value is correct."); + + is(objectSubItem0.value, 7, "The first object sub-item value is correct."); + is(objectSubItem1.value, 6, "The second object sub-item value is correct."); + + let array__proto__ = objectItem5.get("__proto__"); + let object__proto__ = objectItem6.get("__proto__"); + + ok(array__proto__, "The array should have a __proto__ property."); + ok(object__proto__, "The object should have a __proto__ property."); + })(); +} + +function testOriginalRawDataIntegrity(arr, obj) { + is(arr[0], 42, "The first array item should not have changed."); + is(arr[1], true, "The second array item should not have changed."); + is(arr[2], "nasu", "The third array item should not have changed."); + is(arr[3], undefined, "The fourth array item should not have changed."); + is(arr[4], null, "The fifth array item should not have changed."); + ok(arr[5] instanceof Array, "The sixth array item should be an Array."); + is(arr[5][0], 0, "The sixth array item should not have changed."); + is(arr[5][1], 1, "The sixth array item should not have changed."); + is(arr[5][2], 2, "The sixth array item should not have changed."); + ok(arr[6] instanceof Object, "The seventh array item should be an Object."); + is(arr[6].prop1, 9, "The seventh array item should not have changed."); + is(arr[6].prop2, 8, "The seventh array item should not have changed."); + + is(obj.p0, 42, "The first object property should not have changed."); + is(obj.p1, true, "The first object property should not have changed."); + is(obj.p2, "nasu", "The first object property should not have changed."); + is(obj.p3, undefined, "The first object property should not have changed."); + is(obj.p4, null, "The first object property should not have changed."); + ok(obj.p5 instanceof Array, "The sixth object property should be an Array."); + is(obj.p5[0], 3, "The sixth object property should not have changed."); + is(obj.p5[1], 4, "The sixth object property should not have changed."); + is(obj.p5[2], 5, "The sixth object property should not have changed."); + ok(obj.p6 instanceof Object, "The seventh object property should be an Object."); + is(obj.p6.prop1, 7, "The seventh object property should not have changed."); + is(obj.p6.prop2, 6, "The seventh object property should not have changed."); +} + +function testAnonymousHeaders(fooScope, anonymousVar, anonymousScope, barVar, bazProperty) { + is(fooScope.header, true, + "A named scope should have a header visible."); + is(fooScope.target.hasAttribute("untitled"), false, + "The non-header attribute should not be applied to scopes with headers."); + + is(anonymousScope.header, false, + "An anonymous scope should have a header visible."); + is(anonymousScope.target.hasAttribute("untitled"), true, + "The non-header attribute should not be applied to scopes without headers."); + + is(barVar.header, true, + "A named variable should have a header visible."); + is(barVar.target.hasAttribute("untitled"), false, + "The non-header attribute should not be applied to variables with headers."); + + is(anonymousVar.header, false, + "An anonymous variable should have a header visible."); + is(anonymousVar.target.hasAttribute("untitled"), true, + "The non-header attribute should not be applied to variables without headers."); +} + +function testPropertyInheritance(fooScope, anonymousVar, anonymousScope, barVar, bazProperty) { + is(fooScope.preventDisableOnChange, gVariablesView.preventDisableOnChange, + "The preventDisableOnChange property should persist from the view to all scopes."); + is(fooScope.preventDescriptorModifiers, gVariablesView.preventDescriptorModifiers, + "The preventDescriptorModifiers property should persist from the view to all scopes."); + is(fooScope.editableNameTooltip, gVariablesView.editableNameTooltip, + "The editableNameTooltip property should persist from the view to all scopes."); + is(fooScope.editableValueTooltip, gVariablesView.editableValueTooltip, + "The editableValueTooltip property should persist from the view to all scopes."); + is(fooScope.editButtonTooltip, gVariablesView.editButtonTooltip, + "The editButtonTooltip property should persist from the view to all scopes."); + is(fooScope.deleteButtonTooltip, gVariablesView.deleteButtonTooltip, + "The deleteButtonTooltip property should persist from the view to all scopes."); + is(fooScope.contextMenuId, gVariablesView.contextMenuId, + "The contextMenuId property should persist from the view to all scopes."); + is(fooScope.separatorStr, gVariablesView.separatorStr, + "The separatorStr property should persist from the view to all scopes."); + is(fooScope.eval, gVariablesView.eval, + "The eval property should persist from the view to all scopes."); + is(fooScope.switch, gVariablesView.switch, + "The switch property should persist from the view to all scopes."); + is(fooScope.delete, gVariablesView.delete, + "The delete property should persist from the view to all scopes."); + is(fooScope.new, gVariablesView.new, + "The new property should persist from the view to all scopes."); + isnot(fooScope.eval, fooScope.switch, + "The eval and switch functions got mixed up in the scope."); + isnot(fooScope.switch, fooScope.delete, + "The eval and switch functions got mixed up in the scope."); + + is(barVar.preventDisableOnChange, gVariablesView.preventDisableOnChange, + "The preventDisableOnChange property should persist from the view to all variables."); + is(barVar.preventDescriptorModifiers, gVariablesView.preventDescriptorModifiers, + "The preventDescriptorModifiers property should persist from the view to all variables."); + is(barVar.editableNameTooltip, gVariablesView.editableNameTooltip, + "The editableNameTooltip property should persist from the view to all variables."); + is(barVar.editableValueTooltip, gVariablesView.editableValueTooltip, + "The editableValueTooltip property should persist from the view to all variables."); + is(barVar.editButtonTooltip, gVariablesView.editButtonTooltip, + "The editButtonTooltip property should persist from the view to all variables."); + is(barVar.deleteButtonTooltip, gVariablesView.deleteButtonTooltip, + "The deleteButtonTooltip property should persist from the view to all variables."); + is(barVar.contextMenuId, gVariablesView.contextMenuId, + "The contextMenuId property should persist from the view to all variables."); + is(barVar.separatorStr, gVariablesView.separatorStr, + "The separatorStr property should persist from the view to all variables."); + is(barVar.eval, gVariablesView.eval, + "The eval property should persist from the view to all variables."); + is(barVar.switch, gVariablesView.switch, + "The switch property should persist from the view to all variables."); + is(barVar.delete, gVariablesView.delete, + "The delete property should persist from the view to all variables."); + is(barVar.new, gVariablesView.new, + "The new property should persist from the view to all variables."); + isnot(barVar.eval, barVar.switch, + "The eval and switch functions got mixed up in the variable."); + isnot(barVar.switch, barVar.delete, + "The eval and switch functions got mixed up in the variable."); + + is(bazProperty.preventDisableOnChange, gVariablesView.preventDisableOnChange, + "The preventDisableOnChange property should persist from the view to all properties."); + is(bazProperty.preventDescriptorModifiers, gVariablesView.preventDescriptorModifiers, + "The preventDescriptorModifiers property should persist from the view to all properties."); + is(bazProperty.editableNameTooltip, gVariablesView.editableNameTooltip, + "The editableNameTooltip property should persist from the view to all properties."); + is(bazProperty.editableValueTooltip, gVariablesView.editableValueTooltip, + "The editableValueTooltip property should persist from the view to all properties."); + is(bazProperty.editButtonTooltip, gVariablesView.editButtonTooltip, + "The editButtonTooltip property should persist from the view to all properties."); + is(bazProperty.deleteButtonTooltip, gVariablesView.deleteButtonTooltip, + "The deleteButtonTooltip property should persist from the view to all properties."); + is(bazProperty.contextMenuId, gVariablesView.contextMenuId, + "The contextMenuId property should persist from the view to all properties."); + is(bazProperty.separatorStr, gVariablesView.separatorStr, + "The separatorStr property should persist from the view to all properties."); + is(bazProperty.eval, gVariablesView.eval, + "The eval property should persist from the view to all properties."); + is(bazProperty.switch, gVariablesView.switch, + "The switch property should persist from the view to all properties."); + is(bazProperty.delete, gVariablesView.delete, + "The delete property should persist from the view to all properties."); + is(bazProperty.new, gVariablesView.new, + "The new property should persist from the view to all properties."); + isnot(bazProperty.eval, bazProperty.switch, + "The eval and switch functions got mixed up in the property."); + isnot(bazProperty.switch, bazProperty.delete, + "The eval and switch functions got mixed up in the property."); +} + +function testClearHierarchy() { + gVariablesView.clearHierarchy(); + ok(!gVariablesView._prevHierarchy.size, + "The previous hierarchy should have been cleared."); + ok(!gVariablesView._currHierarchy.size, + "The current hierarchy should have been cleared."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVariablesView = null; + gScope = null; + gVariable = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-cancel.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-cancel.js new file mode 100644 index 000000000..dd4954717 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-cancel.js @@ -0,0 +1,58 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +
+/**
+ * Make sure that canceling a name change correctly unhides the separator and
+ * value elements.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_watch-expressions.html";
+
+function test() {
+ Task.spawn(function* () {
+ let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options);
+ let win = panel.panelWin;
+ let vars = win.DebuggerView.Variables;
+
+ win.DebuggerView.WatchExpressions.addExpression("this");
+
+ callInTab(tab, "ermahgerd");
+ yield waitForDebuggerEvents(panel, win.EVENTS.FETCHED_WATCH_EXPRESSIONS);
+
+ let exprScope = vars.getScopeAtIndex(0);
+ let {target} = exprScope.get("this");
+
+ let name = target.querySelector(".title > .name");
+ let separator = target.querySelector(".separator");
+ let value = target.querySelector(".value");
+
+ is(separator.hidden, false,
+ "The separator element should not be hidden.");
+ is(value.hidden, false,
+ "The value element should not be hidden.");
+
+ for (let key of ["ESCAPE", "RETURN"]) {
+ EventUtils.sendMouseEvent({ type: "dblclick" }, name, win);
+
+ is(separator.hidden, true,
+ "The separator element should be hidden.");
+ is(value.hidden, true,
+ "The value element should be hidden.");
+
+ EventUtils.sendKey(key, win);
+
+ is(separator.hidden, false,
+ "The separator element should not be hidden.");
+ is(value.hidden, false,
+ "The value element should not be hidden.");
+ }
+
+ yield resumeDebuggerThenCloseAndFinish(panel);
+ });
+}
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-click.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-click.js new file mode 100644 index 000000000..9ea9230ef --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-click.js @@ -0,0 +1,58 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Check that the editing state of a Variable is correctly tracked. Clicking on + * the textbox while editing should not cancel editing. + */ + +const TAB_URL = EXAMPLE_URL + "doc_watch-expressions.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab, debuggee, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let vars = win.DebuggerView.Variables; + + win.DebuggerView.WatchExpressions.addExpression("this"); + + // Allow this generator function to yield first. + executeSoon(() => debuggee.ermahgerd()); + yield waitForDebuggerEvents(panel, win.EVENTS.FETCHED_WATCH_EXPRESSIONS); + + let exprScope = vars.getScopeAtIndex(0); + let exprVar = exprScope.get("this"); + let name = exprVar.target.querySelector(".title > .name"); + + is(exprVar.editing, false, + "The expression should indicate it is not being edited."); + + EventUtils.sendMouseEvent({ type: "dblclick" }, name, win); + let input = exprVar.target.querySelector(".title > .element-name-input"); + is(exprVar.editing, true, + "The expression should indicate it is being edited."); + is(input.selectionStart !== input.selectionEnd, true, + "The expression text should be selected."); + + EventUtils.synthesizeMouse(input, 2, 2, {}, win); + is(exprVar.editing, true, + "The expression should indicate it is still being edited after a click."); + is(input.selectionStart === input.selectionEnd, true, + "The expression text should not be selected."); + + EventUtils.sendKey("ESCAPE", win); + is(exprVar.editing, false, + "The expression should indicate it is not being edited after cancelling."); + + // Why is this needed? + EventUtils.synthesizeMouse(vars.parentNode, 2, 2, {}, win); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-01.js new file mode 100644 index 000000000..5b5fce266 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-01.js @@ -0,0 +1,300 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view knows how to edit getters and setters. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +var gTab, gPanel, gDebugger; +var gL10N, gEditor, gVars, gWatch; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(2); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gL10N = gDebugger.L10N; + gEditor = gDebugger.DebuggerView.editor; + gVars = gDebugger.DebuggerView.Variables; + gWatch = gDebugger.DebuggerView.WatchExpressions; + + gVars.switch = function () {}; + gVars.delete = function () {}; + + waitForCaretAndScopes(gPanel, 24) + .then(() => addWatchExpressions()) + .then(() => testEdit("set", "this._prop = value + ' BEER CAN'", { + "myVar.prop": "xlerb BEER CAN", + "myVar.prop + 42": "xlerb BEER CAN42", + "myVar.prop = 'xlerb'": "xlerb" + })) + .then(() => testEdit("set", "{ this._prop = value + ' BEACON' }", { + "myVar.prop": "xlerb BEACON", + "myVar.prop + 42": "xlerb BEACON42", + "myVar.prop = 'xlerb'": "xlerb" + })) + .then(() => testEdit("set", "{ this._prop = value + ' BEACON;'; }", { + "myVar.prop": "xlerb BEACON;", + "myVar.prop + 42": "xlerb BEACON;42", + "myVar.prop = 'xlerb'": "xlerb" + })) + .then(() => testEdit("set", "{ return this._prop = value + ' BEACON;;'; }", { + "myVar.prop": "xlerb BEACON;;", + "myVar.prop + 42": "xlerb BEACON;;42", + "myVar.prop = 'xlerb'": "xlerb" + })) + .then(() => testEdit("set", "function(value) { this._prop = value + ' BACON' }", { + "myVar.prop": "xlerb BACON", + "myVar.prop + 42": "xlerb BACON42", + "myVar.prop = 'xlerb'": "xlerb" + })) + .then(() => testEdit("get", "'brelx BEER CAN'", { + "myVar.prop": "brelx BEER CAN", + "myVar.prop + 42": "brelx BEER CAN42", + "myVar.prop = 'xlerb'": "xlerb" + })) + .then(() => testEdit("get", "{ 'brelx BEACON' }", { + "myVar.prop": undefined, + "myVar.prop + 42": NaN, + "myVar.prop = 'xlerb'": "xlerb" + })) + .then(() => testEdit("get", "{ 'brelx BEACON;'; }", { + "myVar.prop": undefined, + "myVar.prop + 42": NaN, + "myVar.prop = 'xlerb'": "xlerb" + })) + .then(() => testEdit("get", "{ return 'brelx BEACON;;'; }", { + "myVar.prop": "brelx BEACON;;", + "myVar.prop + 42": "brelx BEACON;;42", + "myVar.prop = 'xlerb'": "xlerb" + })) + .then(() => testEdit("get", "function() { return 'brelx BACON'; }", { + "myVar.prop": "brelx BACON", + "myVar.prop + 42": "brelx BACON42", + "myVar.prop = 'xlerb'": "xlerb" + })) + .then(() => testEdit("get", "bogus", { + "myVar.prop": "ReferenceError: bogus is not defined", + "myVar.prop + 42": "ReferenceError: bogus is not defined", + "myVar.prop = 'xlerb'": "xlerb" + })) + .then(() => testEdit("set", "sugob", { + "myVar.prop": "ReferenceError: bogus is not defined", + "myVar.prop + 42": "ReferenceError: bogus is not defined", + "myVar.prop = 'xlerb'": "ReferenceError: sugob is not defined" + })) + .then(() => testEdit("get", "", { + "myVar.prop": undefined, + "myVar.prop + 42": NaN, + "myVar.prop = 'xlerb'": "ReferenceError: sugob is not defined" + })) + .then(() => testEdit("set", "", { + "myVar.prop": "xlerb", + "myVar.prop + 42": NaN, + "myVar.prop = 'xlerb'": "xlerb" + })) + .then(() => deleteWatchExpression("myVar.prop = 'xlerb'")) + .then(() => testEdit("self", "2507", { + "myVar.prop": 2507, + "myVar.prop + 42": 2549 + })) + .then(() => deleteWatchExpression("myVar.prop + 42")) + .then(() => testEdit("self", "0910", { + "myVar.prop": 910 + })) + .then(() => deleteLastWatchExpression("myVar.prop")) + .then(() => testWatchExpressionsRemoved()) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); +} + +function addWatchExpressions() { + return promise.resolve(null) + .then(() => { + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS); + gWatch.addExpression("myVar.prop"); + gEditor.focus(); + return finished; + }) + .then(() => { + let exprScope = gVars.getScopeAtIndex(0); + ok(exprScope, + "There should be a wach expressions scope in the variables view."); + is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"), + "The scope's name should be marked as 'Watch Expressions'."); + is(exprScope._store.size, 1, + "There should be 1 evaluation available."); + + let w1 = exprScope.get("myVar.prop"); + let w2 = exprScope.get("myVar.prop + 42"); + let w3 = exprScope.get("myVar.prop = 'xlerb'"); + + ok(w1, "The first watch expression should be present in the scope."); + ok(!w2, "The second watch expression should not be present in the scope."); + ok(!w3, "The third watch expression should not be present in the scope."); + + is(w1.value, 42, "The first value is correct."); + }) + .then(() => { + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS); + gWatch.addExpression("myVar.prop + 42"); + gEditor.focus(); + return finished; + }) + .then(() => { + let exprScope = gVars.getScopeAtIndex(0); + ok(exprScope, + "There should be a wach expressions scope in the variables view."); + is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"), + "The scope's name should be marked as 'Watch Expressions'."); + is(exprScope._store.size, 2, + "There should be 2 evaluations available."); + + let w1 = exprScope.get("myVar.prop"); + let w2 = exprScope.get("myVar.prop + 42"); + let w3 = exprScope.get("myVar.prop = 'xlerb'"); + + ok(w1, "The first watch expression should be present in the scope."); + ok(w2, "The second watch expression should be present in the scope."); + ok(!w3, "The third watch expression should not be present in the scope."); + + is(w1.value, "42", "The first expression value is correct."); + is(w2.value, "84", "The second expression value is correct."); + }) + .then(() => { + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS); + gWatch.addExpression("myVar.prop = 'xlerb'"); + gEditor.focus(); + return finished; + }) + .then(() => { + let exprScope = gVars.getScopeAtIndex(0); + ok(exprScope, + "There should be a wach expressions scope in the variables view."); + is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"), + "The scope's name should be marked as 'Watch Expressions'."); + is(exprScope._store.size, 3, + "There should be 3 evaluations available."); + + let w1 = exprScope.get("myVar.prop"); + let w2 = exprScope.get("myVar.prop + 42"); + let w3 = exprScope.get("myVar.prop = 'xlerb'"); + + ok(w1, "The first watch expression should be present in the scope."); + ok(w2, "The second watch expression should be present in the scope."); + ok(w3, "The third watch expression should be present in the scope."); + + is(w1.value, "xlerb", "The first expression value is correct."); + is(w2.value, "xlerb42", "The second expression value is correct."); + is(w3.value, "xlerb", "The third expression value is correct."); + }); +} + +function deleteWatchExpression(aString) { + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS); + gWatch.deleteExpression({ name: aString }); + return finished; +} + +function deleteLastWatchExpression(aString) { + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES); + gWatch.deleteExpression({ name: aString }); + return finished; +} + +function testEdit(aWhat, aString, aExpected) { + let localScope = gVars.getScopeAtIndex(1); + let myVar = localScope.get("myVar"); + + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES).then(() => { + let propVar = myVar.get("prop"); + let getterOrSetterOrVar = aWhat != "self" ? propVar.get(aWhat) : propVar; + + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS).then(() => { + let exprScope = gVars.getScopeAtIndex(0); + ok(exprScope, + "There should be a wach expressions scope in the variables view."); + is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"), + "The scope's name should be marked as 'Watch Expressions'."); + is(exprScope._store.size, Object.keys(aExpected).length, + "There should be a certain number of evaluations available."); + + function testExpression(aExpression) { + if (!aExpression) { + return; + } + let value = aExpected[aExpression.name]; + if (isNaN(value)) { + ok(isNaN(aExpression.value), + "The expression value is correct after the edit."); + } else if (value == null) { + is(aExpression.value.type, value + "", + "The expression value is correct after the edit."); + } else { + is(aExpression.value, value, + "The expression value is correct after the edit."); + } + } + + testExpression(exprScope.get(Object.keys(aExpected)[0])); + testExpression(exprScope.get(Object.keys(aExpected)[1])); + testExpression(exprScope.get(Object.keys(aExpected)[2])); + }); + + let editTarget = getterOrSetterOrVar.target; + + // Allow the target variable to get painted, so that clicking on + // its value would scroll the new textbox node into view. + executeSoon(() => { + let varValue = editTarget.querySelector(".title > .value"); + EventUtils.sendMouseEvent({ type: "mousedown" }, varValue, gDebugger); + + let varInput = editTarget.querySelector(".title > .element-value-input"); + setText(varInput, aString); + EventUtils.sendKey("RETURN", gDebugger); + }); + + return finished; + }); + + myVar.expand(); + gVars.clearHierarchy(); + + return finished; +} + +function testWatchExpressionsRemoved() { + let scope = gVars.getScopeAtIndex(0); + ok(scope, + "There should be a local scope in the variables view."); + isnot(scope.name, gL10N.getStr("watchExpressionsScopeLabel"), + "The scope's name should not be marked as 'Watch Expressions'."); + isnot(scope._store.size, 0, + "There should be some variables available."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gL10N = null; + gEditor = null; + gVars = null; + gWatch = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-02.js new file mode 100644 index 000000000..c0455a189 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-02.js @@ -0,0 +1,107 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view is able to override getter properties + * to plain value properties. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +var gTab, gPanel, gDebugger; +var gL10N, gEditor, gVars, gWatch; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(2); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gL10N = gDebugger.L10N; + gEditor = gDebugger.DebuggerView.editor; + gVars = gDebugger.DebuggerView.Variables; + gWatch = gDebugger.DebuggerView.WatchExpressions; + + gVars.switch = function () {}; + gVars.delete = function () {}; + + waitForCaretAndScopes(gPanel, 24) + .then(() => addWatchExpression()) + .then(() => testEdit("\"xlerb\"", "xlerb")) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); +} + +function addWatchExpression() { + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS); + + gWatch.addExpression("myVar.prop"); + gEditor.focus(); + + return finished; +} + +function testEdit(aString, aExpected) { + let localScope = gVars.getScopeAtIndex(1); + let myVar = localScope.get("myVar"); + + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES).then(() => { + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS).then(() => { + let exprScope = gVars.getScopeAtIndex(0); + + ok(exprScope, + "There should be a wach expressions scope in the variables view."); + is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"), + "The scope's name should be marked as 'Watch Expressions'."); + is(exprScope._store.size, 1, + "There should be one evaluation available."); + + is(exprScope.get("myVar.prop").value, aExpected, + "The expression value is correct after the edit."); + }); + + let editTarget = myVar.get("prop").target; + + // Allow the target variable to get painted, so that clicking on + // its value would scroll the new textbox node into view. + executeSoon(() => { + let varEdit = editTarget.querySelector(".title > .variables-view-edit"); + EventUtils.sendMouseEvent({ type: "mousedown" }, varEdit, gDebugger); + + let varInput = editTarget.querySelector(".title > .element-value-input"); + setText(varInput, aString); + EventUtils.sendKey("RETURN", gDebugger); + }); + + return finished; + }); + + myVar.expand(); + gVars.clearHierarchy(); + + return finished; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gL10N = null; + gEditor = null; + gVars = null; + gWatch = null; +}); + diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-value.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-value.js new file mode 100644 index 000000000..7fe887152 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-value.js @@ -0,0 +1,91 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the editing variables or properties values works properly. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +var gTab, gPanel, gDebugger; +var gVars; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVars = gDebugger.DebuggerView.Variables; + + waitForCaretAndScopes(gPanel, 24) + .then(() => initialChecks()) + .then(() => testModification("a", "1")) + .then(() => testModification("{ a: 1 }", "Object")) + .then(() => testModification("[a]", "Array[1]")) + .then(() => testModification("b", "Object")) + .then(() => testModification("b.a", "1")) + .then(() => testModification("c.a", "1")) + .then(() => testModification("Infinity", "Infinity")) + .then(() => testModification("NaN", "NaN")) + .then(() => testModification("new Function", "anonymous()")) + .then(() => testModification("+0", "0")) + .then(() => testModification("-0", "-0")) + .then(() => testModification("Object.keys({})", "Array[0]")) + .then(() => testModification("document.title", '"Debugger test page"')) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); +} + +function initialChecks() { + let localScope = gVars.getScopeAtIndex(0); + let aVar = localScope.get("a"); + + is(aVar.target.querySelector(".name").getAttribute("value"), "a", + "Should have the right name for 'a'."); + is(aVar.target.querySelector(".value").getAttribute("value"), "1", + "Should have the right initial value for 'a'."); +} + +function testModification(aNewValue, aNewResult) { + let localScope = gVars.getScopeAtIndex(0); + let aVar = localScope.get("a"); + + // Allow the target variable to get painted, so that clicking on + // its value would scroll the new textbox node into view. + executeSoon(() => { + let varValue = aVar.target.querySelector(".title > .value"); + EventUtils.sendMouseEvent({ type: "mousedown" }, varValue, gDebugger); + + let varInput = aVar.target.querySelector(".title > .element-value-input"); + setText(varInput, aNewValue); + EventUtils.sendKey("RETURN", gDebugger); + }); + + return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => { + let localScope = gVars.getScopeAtIndex(0); + let aVar = localScope.get("a"); + + is(aVar.target.querySelector(".name").getAttribute("value"), "a", + "Should have the right name for 'a'."); + is(aVar.target.querySelector(".value").getAttribute("value"), aNewResult, + "Should have the right new value for 'a'."); + }); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVars = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-watch.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-watch.js new file mode 100644 index 000000000..0271f3738 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-watch.js @@ -0,0 +1,510 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the editing or removing watch expressions works properly. + */ + +const TAB_URL = EXAMPLE_URL + "doc_watch-expressions.html"; + +var gTab, gPanel, gDebugger; +var gL10N, gEditor, gVars, gWatch; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gL10N = gDebugger.L10N; + gEditor = gDebugger.DebuggerView.editor; + gVars = gDebugger.DebuggerView.Variables; + gWatch = gDebugger.DebuggerView.WatchExpressions; + + promise.all([ + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS), + waitForCaretAndScopes(gPanel, 18)]) + .then(() => testInitialVariablesInScope()) + .then(() => testInitialExpressionsInScope()) + .then(() => testModification("document.title = 42", "document.title = 43", "43", "undefined")) + .then(() => testIntegrity1()) + .then(() => testModification("aArg", "aArg = 44", "44", "44")) + .then(() => testIntegrity2()) + .then(() => testModification("aArg = 44", "\ \t\r\ndocument.title\ \t\r\n", "\"43\"", "44")) + .then(() => testIntegrity3()) + .then(() => testModification("document.title = 43", "\ \t\r\ndocument.title\ \t\r\n", "\"43\"", "44")) + .then(() => testIntegrity4()) + .then(() => testModification("document.title", "\ \t\r\n", "\"43\"", "44")) + .then(() => testIntegrity5()) + .then(() => testExprDeletion("this", "44")) + .then(() => testIntegrity6()) + .then(() => testExprFinalDeletion("ermahgerd", "44")) + .then(() => testIntegrity7()) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + addExpressions(); + callInTab(gTab, "ermahgerd"); + }); +} + +function addExpressions() { + addExpression("this"); + addExpression("ermahgerd"); + addExpression("aArg"); + addExpression("document.title"); + addCmdExpression("document.title = 42"); + + is(gWatch.itemCount, 5, + "There should be 5 items availalble in the watch expressions view."); + + is(gWatch.getItemAtIndex(4).attachment.initialExpression, "this", + "The first expression's initial value should be correct."); + is(gWatch.getItemAtIndex(3).attachment.initialExpression, "ermahgerd", + "The second expression's initial value should be correct."); + is(gWatch.getItemAtIndex(2).attachment.initialExpression, "aArg", + "The third expression's initial value should be correct."); + is(gWatch.getItemAtIndex(1).attachment.initialExpression, "document.title", + "The fourth expression's initial value should be correct."); + is(gWatch.getItemAtIndex(0).attachment.initialExpression, "document.title = 42", + "The fifth expression's initial value should be correct."); + + is(gWatch.getItemAtIndex(4).attachment.currentExpression, "this", + "The first expression's current value should be correct."); + is(gWatch.getItemAtIndex(3).attachment.currentExpression, "ermahgerd", + "The second expression's current value should be correct."); + is(gWatch.getItemAtIndex(2).attachment.currentExpression, "aArg", + "The third expression's current value should be correct."); + is(gWatch.getItemAtIndex(1).attachment.currentExpression, "document.title", + "The fourth expression's current value should be correct."); + is(gWatch.getItemAtIndex(0).attachment.currentExpression, "document.title = 42", + "The fifth expression's current value should be correct."); +} + +function testInitialVariablesInScope() { + let localScope = gVars.getScopeAtIndex(1); + let argVar = localScope.get("aArg"); + + is(argVar.visible, true, + "Should have the right visibility state for 'aArg'."); + is(argVar.name, "aArg", + "Should have the right name for 'aArg'."); + is(argVar.value.type, "undefined", + "Should have the right initial value for 'aArg'."); +} + +function testInitialExpressionsInScope() { + let exprScope = gVars.getScopeAtIndex(0); + let thisExpr = exprScope.get("this"); + let ermExpr = exprScope.get("ermahgerd"); + let argExpr = exprScope.get("aArg"); + let docExpr = exprScope.get("document.title"); + let docExpr2 = exprScope.get("document.title = 42"); + + ok(exprScope, + "There should be a wach expressions scope in the variables view."); + is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"), + "The scope's name should be marked as 'Watch Expressions'."); + is(exprScope._store.size, 5, + "There should be 5 evaluations available."); + + is(thisExpr.visible, true, + "Should have the right visibility state for 'this'."); + is(thisExpr.target.querySelectorAll(".variables-view-delete").length, 1, + "Should have the one close button visible for 'this'."); + is(thisExpr.name, "this", + "Should have the right name for 'this'."); + is(thisExpr.value.type, "object", + "Should have the right value type for 'this'."); + is(thisExpr.value.class, "Window", + "Should have the right value type for 'this'."); + + is(ermExpr.visible, true, + "Should have the right visibility state for 'ermahgerd'."); + is(ermExpr.target.querySelectorAll(".variables-view-delete").length, 1, + "Should have the one close button visible for 'ermahgerd'."); + is(ermExpr.name, "ermahgerd", + "Should have the right name for 'ermahgerd'."); + is(ermExpr.value.type, "object", + "Should have the right value type for 'ermahgerd'."); + is(ermExpr.value.class, "Function", + "Should have the right value type for 'ermahgerd'."); + + is(argExpr.visible, true, + "Should have the right visibility state for 'aArg'."); + is(argExpr.target.querySelectorAll(".variables-view-delete").length, 1, + "Should have the one close button visible for 'aArg'."); + is(argExpr.name, "aArg", + "Should have the right name for 'aArg'."); + is(argExpr.value.type, "undefined", + "Should have the right value for 'aArg'."); + + is(docExpr.visible, true, + "Should have the right visibility state for 'document.title'."); + is(docExpr.target.querySelectorAll(".variables-view-delete").length, 1, + "Should have the one close button visible for 'document.title'."); + is(docExpr.name, "document.title", + "Should have the right name for 'document.title'."); + is(docExpr.value, "42", + "Should have the right value for 'document.title'."); + + is(docExpr2.visible, true, + "Should have the right visibility state for 'document.title = 42'."); + is(docExpr2.target.querySelectorAll(".variables-view-delete").length, 1, + "Should have the one close button visible for 'document.title = 42'."); + is(docExpr2.name, "document.title = 42", + "Should have the right name for 'document.title = 42'."); + is(docExpr2.value, 42, + "Should have the right value for 'document.title = 42'."); + + is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 5, + "There should be 5 hidden nodes in the watch expressions container."); + is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0, + "There should be 0 visible nodes in the watch expressions container."); +} + +function testModification(aName, aNewValue, aNewResult, aArgResult) { + let exprScope = gVars.getScopeAtIndex(0); + let exprVar = exprScope.get(aName); + + let finished = promise.all([ + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS) + ]) + .then(() => { + let localScope = gVars.getScopeAtIndex(1); + let argVar = localScope.get("aArg"); + + is(argVar.visible, true, + "Should have the right visibility state for 'aArg'."); + is(argVar.target.querySelector(".name").getAttribute("value"), "aArg", + "Should have the right name for 'aArg'."); + is(argVar.target.querySelector(".value").getAttribute("value"), aArgResult, + "Should have the right new value for 'aArg'."); + + let exprScope = gVars.getScopeAtIndex(0); + let exprOldVar = exprScope.get(aName); + let exprNewVar = exprScope.get(aNewValue.trim()); + + if (!aNewValue.trim()) { + ok(!exprOldVar, + "The old watch expression should have been removed."); + ok(!exprNewVar, + "No new watch expression should have been added."); + } else { + ok(!exprOldVar, + "The old watch expression should have been removed."); + ok(exprNewVar, + "The new watch expression should have been added."); + + is(exprNewVar.visible, true, + "Should have the right visibility state for the watch expression."); + is(exprNewVar.target.querySelector(".name").getAttribute("value"), aNewValue.trim(), + "Should have the right name for the watch expression."); + is(exprNewVar.target.querySelector(".value").getAttribute("value"), aNewResult, + "Should have the right new value for the watch expression."); + } + }); + + let varValue = exprVar.target.querySelector(".title > .name"); + EventUtils.sendMouseEvent({ type: "dblclick" }, varValue, gDebugger); + + let varInput = exprVar.target.querySelector(".title > .element-name-input"); + setText(varInput, aNewValue); + EventUtils.sendKey("RETURN", gDebugger); + + return finished; +} + +function testExprDeletion(aName, aArgResult) { + let exprScope = gVars.getScopeAtIndex(0); + let exprVar = exprScope.get(aName); + + let finished = promise.all([ + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS) + ]) + .then(() => { + let localScope = gVars.getScopeAtIndex(1); + let argVar = localScope.get("aArg"); + + is(argVar.visible, true, + "Should have the right visibility state for 'aArg'."); + is(argVar.target.querySelector(".name").getAttribute("value"), "aArg", + "Should have the right name for 'aArg'."); + is(argVar.target.querySelector(".value").getAttribute("value"), aArgResult, + "Should have the right new value for 'aArg'."); + + let exprScope = gVars.getScopeAtIndex(0); + let exprOldVar = exprScope.get(aName); + + ok(!exprOldVar, + "The watch expression should have been deleted."); + }); + + let varDelete = exprVar.target.querySelector(".variables-view-delete"); + EventUtils.sendMouseEvent({ type: "click" }, varDelete, gDebugger); + + return finished; +} + +function testExprFinalDeletion(aName, aArgResult) { + let exprScope = gVars.getScopeAtIndex(0); + let exprVar = exprScope.get(aName); + + let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => { + let localScope = gVars.getScopeAtIndex(0); + let argVar = localScope.get("aArg"); + + is(argVar.visible, true, + "Should have the right visibility state for 'aArg'."); + is(argVar.target.querySelector(".name").getAttribute("value"), "aArg", + "Should have the right name for 'aArg'."); + is(argVar.target.querySelector(".value").getAttribute("value"), aArgResult, + "Should have the right new value for 'aArg'."); + + let exprScope = gVars.getScopeAtIndex(0); + let exprOldVar = exprScope.get(aName); + + ok(!exprOldVar, + "The watch expression should have been deleted."); + }); + + let varDelete = exprVar.target.querySelector(".variables-view-delete"); + EventUtils.sendMouseEvent({ type: "click" }, varDelete, gDebugger); + + return finished; +} + +function testIntegrity1() { + is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 5, + "There should be 5 hidden nodes in the watch expressions container."); + is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0, + "There should be 0 visible nodes in the watch expressions container."); + + let exprScope = gVars.getScopeAtIndex(0); + ok(exprScope, + "There should be a wach expressions scope in the variables view."); + is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"), + "The scope's name should be marked as 'Watch Expressions'."); + is(exprScope._store.size, 5, + "There should be 5 visible evaluations available."); + + is(gWatch.itemCount, 5, + "There should be 5 hidden expression input available."); + is(gWatch.getItemAtIndex(0).attachment.view.inputNode.value, "document.title = 43", + "The first textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(0).attachment.currentExpression, "document.title = 43", + "The first textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(1).attachment.view.inputNode.value, "document.title", + "The second textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(1).attachment.currentExpression, "document.title", + "The second textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(2).attachment.view.inputNode.value, "aArg", + "The third textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(2).attachment.currentExpression, "aArg", + "The third textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(3).attachment.view.inputNode.value, "ermahgerd", + "The fourth textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(3).attachment.currentExpression, "ermahgerd", + "The fourth textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(4).attachment.view.inputNode.value, "this", + "The fifth textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(4).attachment.currentExpression, "this", + "The fifth textbox input value is not the correct one."); +} + +function testIntegrity2() { + is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 5, + "There should be 5 hidden nodes in the watch expressions container."); + is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0, + "There should be 0 visible nodes in the watch expressions container."); + + let exprScope = gVars.getScopeAtIndex(0); + ok(exprScope, + "There should be a wach expressions scope in the variables view."); + is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"), + "The scope's name should be marked as 'Watch Expressions'."); + is(exprScope._store.size, 5, + "There should be 5 visible evaluations available."); + + is(gWatch.itemCount, 5, + "There should be 5 hidden expression input available."); + is(gWatch.getItemAtIndex(0).attachment.view.inputNode.value, "document.title = 43", + "The first textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(0).attachment.currentExpression, "document.title = 43", + "The first textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(1).attachment.view.inputNode.value, "document.title", + "The second textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(1).attachment.currentExpression, "document.title", + "The second textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(2).attachment.view.inputNode.value, "aArg = 44", + "The third textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(2).attachment.currentExpression, "aArg = 44", + "The third textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(3).attachment.view.inputNode.value, "ermahgerd", + "The fourth textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(3).attachment.currentExpression, "ermahgerd", + "The fourth textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(4).attachment.view.inputNode.value, "this", + "The fifth textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(4).attachment.currentExpression, "this", + "The fifth textbox input value is not the correct one."); +} + +function testIntegrity3() { + is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 4, + "There should be 4 hidden nodes in the watch expressions container."); + is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0, + "There should be 0 visible nodes in the watch expressions container."); + + let exprScope = gVars.getScopeAtIndex(0); + ok(exprScope, + "There should be a wach expressions scope in the variables view."); + is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"), + "The scope's name should be marked as 'Watch Expressions'."); + is(exprScope._store.size, 4, + "There should be 4 visible evaluations available."); + + is(gWatch.itemCount, 4, + "There should be 4 hidden expression input available."); + is(gWatch.getItemAtIndex(0).attachment.view.inputNode.value, "document.title = 43", + "The first textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(0).attachment.currentExpression, "document.title = 43", + "The first textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(1).attachment.view.inputNode.value, "document.title", + "The second textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(1).attachment.currentExpression, "document.title", + "The second textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(2).attachment.view.inputNode.value, "ermahgerd", + "The third textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(2).attachment.currentExpression, "ermahgerd", + "The third textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(3).attachment.view.inputNode.value, "this", + "The fourth textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(3).attachment.currentExpression, "this", + "The fourth textbox input value is not the correct one."); +} + +function testIntegrity4() { + is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 3, + "There should be 3 hidden nodes in the watch expressions container."); + is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0, + "There should be 0 visible nodes in the watch expressions container."); + + let exprScope = gVars.getScopeAtIndex(0); + ok(exprScope, + "There should be a wach expressions scope in the variables view."); + is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"), + "The scope's name should be marked as 'Watch Expressions'."); + is(exprScope._store.size, 3, + "There should be 3 visible evaluations available."); + + is(gWatch.itemCount, 3, + "There should be 3 hidden expression input available."); + is(gWatch.getItemAtIndex(0).attachment.view.inputNode.value, "document.title", + "The first textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(0).attachment.currentExpression, "document.title", + "The first textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(1).attachment.view.inputNode.value, "ermahgerd", + "The second textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(1).attachment.currentExpression, "ermahgerd", + "The second textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(2).attachment.view.inputNode.value, "this", + "The third textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(2).attachment.currentExpression, "this", + "The third textbox input value is not the correct one."); +} + +function testIntegrity5() { + is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 2, + "There should be 2 hidden nodes in the watch expressions container."); + is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0, + "There should be 0 visible nodes in the watch expressions container."); + + let exprScope = gVars.getScopeAtIndex(0); + ok(exprScope, + "There should be a wach expressions scope in the variables view."); + is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"), + "The scope's name should be marked as 'Watch Expressions'."); + is(exprScope._store.size, 2, + "There should be 2 visible evaluations available."); + + is(gWatch.itemCount, 2, + "There should be 2 hidden expression input available."); + is(gWatch.getItemAtIndex(0).attachment.view.inputNode.value, "ermahgerd", + "The first textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(0).attachment.currentExpression, "ermahgerd", + "The first textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(1).attachment.view.inputNode.value, "this", + "The second textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(1).attachment.currentExpression, "this", + "The second textbox input value is not the correct one."); +} + +function testIntegrity6() { + is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 1, + "There should be 1 hidden nodes in the watch expressions container."); + is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0, + "There should be 0 visible nodes in the watch expressions container."); + + let exprScope = gVars.getScopeAtIndex(0); + ok(exprScope, + "There should be a wach expressions scope in the variables view."); + is(exprScope.name, gL10N.getStr("watchExpressionsScopeLabel"), + "The scope's name should be marked as 'Watch Expressions'."); + is(exprScope._store.size, 1, + "There should be 1 visible evaluation available."); + + is(gWatch.itemCount, 1, + "There should be 1 hidden expression input available."); + is(gWatch.getItemAtIndex(0).attachment.view.inputNode.value, "ermahgerd", + "The first textbox input value is not the correct one."); + is(gWatch.getItemAtIndex(0).attachment.currentExpression, "ermahgerd", + "The first textbox input value is not the correct one."); +} + +function testIntegrity7() { + is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 0, + "There should be 0 hidden nodes in the watch expressions container."); + is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0, + "There should be 0 visible nodes in the watch expressions container."); + + let localScope = gVars.getScopeAtIndex(0); + ok(localScope, + "There should be a local scope in the variables view."); + isnot(localScope.name, gL10N.getStr("watchExpressionsScopeLabel"), + "The scope's name should not be marked as 'Watch Expressions'."); + isnot(localScope._store.size, 0, + "There should be some variables available."); + + is(gWatch.itemCount, 0, + "The watch expressions container should be empty."); +} + +function addExpression(aString) { + gWatch.addExpression(aString); + gEditor.focus(); +} + +function addCmdExpression(aString) { + gWatch._onCmdAddExpression(aString); + gEditor.focus(); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gL10N = null; + gEditor = null; + gVars = null; + gWatch = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-01.js new file mode 100644 index 000000000..f2c142c85 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-01.js @@ -0,0 +1,241 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view correctly filters nodes by name. + */ + +const TAB_URL = EXAMPLE_URL + "doc_with-frame.html"; + +var gTab, gPanel, gDebugger; +var gVariables, gSearchBox; + +function test() { + // Debug test slaves are quite slow at this test. + requestLongerTimeout(4); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVariables = gDebugger.DebuggerView.Variables; + + gVariables._enableSearch(); + gSearchBox = gVariables._searchboxNode; + + // The first 'with' scope should be expanded by default, but the + // variables haven't been fetched yet. This is how 'with' scopes work. + promise.all([ + waitForCaretAndScopes(gPanel, 22), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES) + ]).then(prepareVariablesAndProperties) + .then(testVariablesAndPropertiesFiltering) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); +} + +function testVariablesAndPropertiesFiltering() { + let localScope = gVariables.getScopeAtIndex(0); + let withScope = gVariables.getScopeAtIndex(1); + let functionScope = gVariables.getScopeAtIndex(2); + let globalLexicalScope = gVariables.getScopeAtIndex(3); + let globalScope = gVariables.getScopeAtIndex(4); + let protoVar = localScope.get("__proto__"); + let constrVar = protoVar.get("constructor"); + let proto2Var = constrVar.get("__proto__"); + let constr2Var = proto2Var.get("constructor"); + + function testFiltered() { + is(localScope.expanded, true, + "The localScope should be expanded."); + is(withScope.expanded, true, + "The withScope should be expanded."); + is(functionScope.expanded, true, + "The functionScope should be expanded."); + is(globalLexicalScope.expanded, true, + "The globalLexicalScope should be expanded."); + is(globalScope.expanded, true, + "The globalScope should be expanded."); + + is(protoVar.expanded, true, + "The protoVar should be expanded."); + is(constrVar.expanded, true, + "The constrVar should be expanded."); + is(proto2Var.expanded, true, + "The proto2Var should be expanded."); + is(constr2Var.expanded, true, + "The constr2Var should be expanded."); + + is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 1, + "There should be 1 variable displayed in the local scope."); + is(withScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0, + "There should be 0 variables displayed in the with scope."); + is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0, + "There should be 0 variables displayed in the function scope."); + is(globalLexicalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0, + "There should be 0 variables displayed in the global lexical scope."); + is(globalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0, + "There should be 0 variables displayed in the global scope."); + + is(withScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0, + "There should be 0 properties displayed in the with scope."); + is(functionScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0, + "There should be 0 properties displayed in the function scope."); + is(globalLexicalScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0, + "There should be 0 properties displayed in the global lexical scope."); + is(globalScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0, + "There should be 0 properties displayed in the global scope."); + + is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"), + "__proto__", "The only inner variable displayed should be '__proto__'"); + is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .name")[0].getAttribute("value"), + "constructor", "The first inner property displayed should be 'constructor'"); + is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .name")[1].getAttribute("value"), + "__proto__", "The second inner property displayed should be '__proto__'"); + is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .name")[2].getAttribute("value"), + "constructor", "The third inner property displayed should be 'constructor'"); + } + + function firstFilter() { + let expanded = once(gVariables, "fetched"); + typeText(gSearchBox, "constructor"); + gSearchBox.doCommand(); + return expanded.then(testFiltered); + } + + function secondFilter() { + localScope.collapse(); + withScope.collapse(); + functionScope.collapse(); + globalLexicalScope.collapse(); + globalScope.collapse(); + protoVar.collapse(); + constrVar.collapse(); + proto2Var.collapse(); + constr2Var.collapse(); + + is(localScope.expanded, false, + "The localScope should not be expanded."); + is(withScope.expanded, false, + "The withScope should not be expanded."); + is(functionScope.expanded, false, + "The functionScope should not be expanded."); + is(globalLexicalScope.expanded, false, + "The globalLexicalScope should not be expanded."); + is(globalScope.expanded, false, + "The globalScope should not be expanded."); + + is(protoVar.expanded, false, + "The protoVar should not be expanded."); + is(constrVar.expanded, false, + "The constrVar should not be expanded."); + is(proto2Var.expanded, false, + "The proto2Var should not be expanded."); + is(constr2Var.expanded, false, + "The constr2Var should not be expanded."); + + let expanded = once(gVariables, "fetched"); + clearText(gSearchBox); + typeText(gSearchBox, "constructor"); + expanded.then(testFiltered); + } + + firstFilter().then(secondFilter); +} + +function prepareVariablesAndProperties() { + let deferred = promise.defer(); + + let localScope = gVariables.getScopeAtIndex(0); + let withScope = gVariables.getScopeAtIndex(1); + let functionScope = gVariables.getScopeAtIndex(2); + let globalLexicalScope = gVariables.getScopeAtIndex(3); + let globalScope = gVariables.getScopeAtIndex(4); + + is(localScope.expanded, true, + "The localScope should be expanded."); + is(withScope.expanded, false, + "The withScope should not be expanded yet."); + is(functionScope.expanded, false, + "The functionScope should not be expanded yet."); + is(globalLexicalScope.expanded, false, + "The globalLexicalScope should not be expanded yet."); + is(globalScope.expanded, false, + "The globalScope should not be expanded yet."); + + // Wait for only two events to be triggered, because the Function scope is + // an environment to which scope arguments and variables are already attached. + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 2).then(() => { + is(localScope.expanded, true, + "The localScope should now be expanded."); + is(withScope.expanded, true, + "The withScope should now be expanded."); + is(functionScope.expanded, true, + "The functionScope should now be expanded."); + is(globalLexicalScope.expanded, true, + "The globalLexicalScope should be expanded."); + is(globalScope.expanded, true, + "The globalScope should now be expanded."); + + let protoVar = localScope.get("__proto__"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + let constrVar = protoVar.get("constructor"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + let proto2Var = constrVar.get("__proto__"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + let constr2Var = proto2Var.get("constructor"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + is(protoVar.expanded, true, + "The local scope '__proto__' should be expanded."); + is(constrVar.expanded, true, + "The local scope '__proto__.constructor' should be expanded."); + is(proto2Var.expanded, true, + "The local scope '__proto__.constructor.__proto__' should be expanded."); + is(constr2Var.expanded, true, + "The local scope '__proto__.constructor.__proto__.constructor' should be expanded."); + + deferred.resolve(); + }); + + constr2Var.expand(); + }); + + proto2Var.expand(); + }); + + constrVar.expand(); + }); + + protoVar.expand(); + }); + + withScope.expand(); + functionScope.expand(); + globalLexicalScope.expand(); + globalScope.expand(); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVariables = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-02.js new file mode 100644 index 000000000..967deb3a5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-02.js @@ -0,0 +1,249 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view correctly filters nodes by value. + */ + +const TAB_URL = EXAMPLE_URL + "doc_with-frame.html"; + +var gTab, gPanel, gDebugger; +var gVariables, gSearchBox; + +function test() { + // Debug test slaves are quite slow at this test. + requestLongerTimeout(4); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVariables = gDebugger.DebuggerView.Variables; + + gVariables._enableSearch(); + gSearchBox = gVariables._searchboxNode; + + // The first 'with' scope should be expanded by default, but the + // variables haven't been fetched yet. This is how 'with' scopes work. + promise.all([ + waitForCaretAndScopes(gPanel, 22), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES) + ]).then(prepareVariablesAndProperties) + .then(testVariablesAndPropertiesFiltering) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); +} + +function testVariablesAndPropertiesFiltering() { + let localScope = gVariables.getScopeAtIndex(0); + let withScope = gVariables.getScopeAtIndex(1); + let functionScope = gVariables.getScopeAtIndex(2); + let globalLexicalScope = gVariables.getScopeAtIndex(3); + let globalScope = gVariables.getScopeAtIndex(4); + let protoVar = localScope.get("__proto__"); + let constrVar = protoVar.get("constructor"); + let proto2Var = constrVar.get("__proto__"); + let constr2Var = proto2Var.get("constructor"); + + function testFiltered() { + is(localScope.expanded, true, + "The localScope should be expanded."); + is(withScope.expanded, true, + "The withScope should be expanded."); + is(functionScope.expanded, true, + "The functionScope should be expanded."); + is(globalLexicalScope.expanded, true, + "The globalScope should be expanded."); + is(globalScope.expanded, true, + "The globalScope should be expanded."); + + is(protoVar.expanded, true, + "The protoVar should be expanded."); + is(constrVar.expanded, true, + "The constrVar should be expanded."); + is(proto2Var.expanded, true, + "The proto2Var should be expanded."); + is(constr2Var.expanded, true, + "The constr2Var should be expanded."); + + is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 1, + "There should be 1 variable displayed in the local scope."); + is(withScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0, + "There should be 0 variables displayed in the with scope."); + is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0, + "There should be 0 variables displayed in the function scope."); + is(globalLexicalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0, + "There should be no variables displayed in the global lexical scope."); + is(globalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0, + "There should be no variables displayed in the global scope."); + + is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 4, + "There should be 4 properties displayed in the local scope."); + is(withScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0, + "There should be 0 properties displayed in the with scope."); + is(functionScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0, + "There should be 0 properties displayed in the function scope."); + is(globalLexicalScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0, + "There should be 0 properties displayed in the global lexical scope."); + is(globalScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0, + "There should be 0 properties displayed in the global scope."); + + is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"), + "__proto__", "The only inner variable displayed should be '__proto__'"); + is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .name")[0].getAttribute("value"), + "constructor", "The first inner property displayed should be 'constructor'"); + is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .name")[1].getAttribute("value"), + "__proto__", "The second inner property displayed should be '__proto__'"); + is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .name")[2].getAttribute("value"), + "constructor", "The third inner property displayed should be 'constructor'"); + + is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .name")[3].getAttribute("value"), + "name", "The fourth inner property displayed should be 'name'"); + is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched]) > .title > .value")[3].getAttribute("value"), + "\"Function\"", "The fourth inner property displayed should be '\"Function\"'"); + } + + function firstFilter() { + let expanded = once(gVariables, "fetched"); + typeText(gSearchBox, "\"Function\""); + gSearchBox.doCommand(); + return expanded.then(testFiltered); + } + + function secondFilter() { + localScope.collapse(); + withScope.collapse(); + functionScope.collapse(); + globalLexicalScope.collapse(); + globalScope.collapse(); + protoVar.collapse(); + constrVar.collapse(); + proto2Var.collapse(); + constr2Var.collapse(); + + is(localScope.expanded, false, + "The localScope should not be expanded."); + is(withScope.expanded, false, + "The withScope should not be expanded."); + is(functionScope.expanded, false, + "The functionScope should not be expanded."); + is(globalLexicalScope.expanded, false, + "The globalScope should not be expanded."); + is(globalScope.expanded, false, + "The globalScope should not be expanded."); + + is(protoVar.expanded, false, + "The protoVar should not be expanded."); + is(constrVar.expanded, false, + "The constrVar should not be expanded."); + is(proto2Var.expanded, false, + "The proto2Var should not be expanded."); + is(constr2Var.expanded, false, + "The constr2Var should not be expanded."); + + backspaceText(gSearchBox, 10); + let expanded = once(gVariables, "fetched"); + typeText(gSearchBox, "\"Function\""); + gSearchBox.doCommand(); + expanded.then(testFiltered); + } + + firstFilter().then(secondFilter); +} + +function prepareVariablesAndProperties() { + let deferred = promise.defer(); + + let localScope = gVariables.getScopeAtIndex(0); + let withScope = gVariables.getScopeAtIndex(1); + let functionScope = gVariables.getScopeAtIndex(2); + let globalLexicalScope = gVariables.getScopeAtIndex(3); + let globalScope = gVariables.getScopeAtIndex(4); + + is(localScope.expanded, true, + "The localScope should be expanded."); + is(withScope.expanded, false, + "The withScope should not be expanded yet."); + is(functionScope.expanded, false, + "The functionScope should not be expanded yet."); + is(globalLexicalScope.expanded, false, + "The globalScope should not be expanded yet."); + is(globalScope.expanded, false, + "The globalScope should not be expanded yet."); + + // Wait for only two events to be triggered, because the Function scope is + // an environment to which scope arguments and variables are already attached. + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 2).then(() => { + is(localScope.expanded, true, + "The localScope should now be expanded."); + is(withScope.expanded, true, + "The withScope should now be expanded."); + is(functionScope.expanded, true, + "The functionScope should now be expanded."); + is(globalLexicalScope.expanded, true, + "The globalScope should now be expanded."); + is(globalScope.expanded, true, + "The globalScope should now be expanded."); + + let protoVar = localScope.get("__proto__"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + let constrVar = protoVar.get("constructor"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + let proto2Var = constrVar.get("__proto__"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + let constr2Var = proto2Var.get("constructor"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + is(protoVar.expanded, true, + "The local scope '__proto__' should be expanded."); + is(constrVar.expanded, true, + "The local scope '__proto__.constructor' should be expanded."); + is(proto2Var.expanded, true, + "The local scope '__proto__.constructor.__proto__' should be expanded."); + is(constr2Var.expanded, true, + "The local scope '__proto__.constructor.__proto__.constructor' should be expanded."); + + deferred.resolve(); + }); + + constr2Var.expand(); + }); + + proto2Var.expand(); + }); + + constrVar.expand(); + }); + + protoVar.expand(); + }); + + withScope.expand(); + functionScope.expand(); + globalLexicalScope.expand(); + globalScope.expand(); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVariables = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-03.js new file mode 100644 index 000000000..cd4927e0f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-03.js @@ -0,0 +1,178 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view correctly filters nodes when triggered + * from the debugger's searchbox via an operator. + */ + +const TAB_URL = EXAMPLE_URL + "doc_with-frame.html"; + +var gTab, gPanel, gDebugger; +var gVariables, gSearchBox; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(2); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVariables = gDebugger.DebuggerView.Variables; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + // The first 'with' scope should be expanded by default, but the + // variables haven't been fetched yet. This is how 'with' scopes work. + promise.all([ + waitForCaretAndScopes(gPanel, 22), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES) + ]).then(prepareVariablesAndProperties) + .then(testVariablesAndPropertiesFiltering) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); +} + +function testVariablesAndPropertiesFiltering() { + let localScope = gVariables.getScopeAtIndex(0); + let withScope = gVariables.getScopeAtIndex(1); + let functionScope = gVariables.getScopeAtIndex(2); + let globalLexicalScope = gVariables.getScopeAtIndex(3); + let globalScope = gVariables.getScopeAtIndex(4); + + function testFiltered() { + is(localScope.expanded, true, + "The localScope should be expanded."); + is(withScope.expanded, true, + "The withScope should be expanded."); + is(functionScope.expanded, true, + "The functionScope should be expanded."); + is(globalLexicalScope.expanded, true, + "The globalScope should be expanded."); + is(globalScope.expanded, true, + "The globalScope should be expanded."); + + is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 1, + "There should be 1 variable displayed in the local scope."); + is(withScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0, + "There should be 0 variables displayed in the with scope."); + is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0, + "There should be 0 variables displayed in the function scope."); + is(globalLexicalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0, + "There should be 0 variables displayed in the global scope."); + is(globalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length, 0, + "There should be 0 variables displayed in the global scope."); + + is(localScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0, + "There should be 0 properties displayed in the local scope."); + is(withScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0, + "There should be 0 properties displayed in the with scope."); + is(functionScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0, + "There should be 0 properties displayed in the function scope."); + is(globalLexicalScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0, + "There should be 0 properties displayed in the global scope."); + is(globalScope.target.querySelectorAll(".variables-view-property:not([unmatched])").length, 0, + "There should be 0 properties displayed in the global scope."); + } + + function firstFilter() { + typeText(gSearchBox, "*alpha"); + testFiltered("alpha"); + + is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"), + "alpha", "The only inner variable displayed should be 'alpha'"); + } + + function secondFilter() { + localScope.collapse(); + withScope.collapse(); + functionScope.collapse(); + globalLexicalScope.collapse(); + globalScope.collapse(); + + is(localScope.expanded, false, + "The localScope should not be expanded."); + is(withScope.expanded, false, + "The withScope should not be expanded."); + is(functionScope.expanded, false, + "The functionScope should not be expanded."); + is(globalLexicalScope.expanded, false, + "The globalScope should not be expanded."); + is(globalScope.expanded, false, + "The globalScope should not be expanded."); + + backspaceText(gSearchBox, 6); + typeText(gSearchBox, "*beta"); + testFiltered("beta"); + + is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"), + "beta", "The only inner variable displayed should be 'beta'"); + } + + firstFilter(); + secondFilter(); +} + +function prepareVariablesAndProperties() { + let deferred = promise.defer(); + + let localScope = gVariables.getScopeAtIndex(0); + let withScope = gVariables.getScopeAtIndex(1); + let functionScope = gVariables.getScopeAtIndex(2); + let globalLexicalScope = gVariables.getScopeAtIndex(3); + let globalScope = gVariables.getScopeAtIndex(4); + + is(localScope.expanded, true, + "The localScope should be expanded."); + is(withScope.expanded, false, + "The withScope should not be expanded yet."); + is(functionScope.expanded, false, + "The functionScope should not be expanded yet."); + is(globalLexicalScope.expanded, false, + "The globalScope should not be expanded yet."); + is(globalScope.expanded, false, + "The globalScope should not be expanded yet."); + + // Wait for only two events to be triggered, because the Function scope is + // an environment to which scope arguments and variables are already attached. + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 2).then(() => { + is(localScope.expanded, true, + "The localScope should now be expanded."); + is(withScope.expanded, true, + "The withScope should now be expanded."); + is(functionScope.expanded, true, + "The functionScope should now be expanded."); + is(globalLexicalScope.expanded, true, + "The globalScope should now be expanded."); + is(globalScope.expanded, true, + "The globalScope should now be expanded."); + + deferred.resolve(); + }); + + withScope.expand(); + functionScope.expand(); + globalLexicalScope.expand(); + globalScope.expand(); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVariables = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-04.js new file mode 100644 index 000000000..0838f1517 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-04.js @@ -0,0 +1,243 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view correctly shows/hides nodes when various + * keyboard shortcuts are pressed in the debugger's searchbox. + */ + +const TAB_URL = EXAMPLE_URL + "doc_with-frame.html"; + +var gTab, gPanel, gDebugger; +var gEditor, gVariables, gSearchBox; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(2); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gVariables = gDebugger.DebuggerView.Variables; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + // The first 'with' scope should be expanded by default, but the + // variables haven't been fetched yet. This is how 'with' scopes work. + promise.all([ + waitForCaretAndScopes(gPanel, 22), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES) + ]).then(prepareVariablesAndProperties) + .then(testVariablesAndPropertiesFiltering) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); +} + +function testVariablesAndPropertiesFiltering() { + let localScope = gVariables.getScopeAtIndex(0); + let withScope = gVariables.getScopeAtIndex(1); + let functionScope = gVariables.getScopeAtIndex(2); + let globalLexicalScope = gVariables.getScopeAtIndex(3); + let globalScope = gVariables.getScopeAtIndex(4); + let step = 0; + + let tests = [ + function () { + assertExpansion([true, false, false, false, false]); + EventUtils.sendKey("RETURN", gDebugger); + }, + function () { + assertExpansion([true, false, false, false, false]); + EventUtils.sendKey("RETURN", gDebugger); + }, + function () { + assertExpansion([true, false, false, false, false]); + gEditor.focus(); + }, + function () { + assertExpansion([true, false, false, false, false]); + typeText(gSearchBox, "*"); + }, + function () { + assertExpansion([true, true, true, true, true]); + EventUtils.sendKey("RETURN", gDebugger); + }, + function () { + assertExpansion([true, true, true, true, true]); + EventUtils.sendKey("RETURN", gDebugger); + }, + function () { + assertExpansion([true, true, true, true, true]); + gEditor.focus(); + }, + function () { + assertExpansion([true, true, true, true, true]); + backspaceText(gSearchBox, 1); + }, + function () { + assertExpansion([true, true, true, true, true]); + EventUtils.sendKey("RETURN", gDebugger); + }, + function () { + assertExpansion([true, true, true, true, true]); + EventUtils.sendKey("RETURN", gDebugger); + }, + function () { + assertExpansion([true, true, true, true, true]); + gEditor.focus(); + }, + function () { + assertExpansion([true, true, true, true, true]); + localScope.collapse(); + withScope.collapse(); + functionScope.collapse(); + globalLexicalScope.collapse(); + globalScope.collapse(); + }, + function () { + assertExpansion([false, false, false, false, false]); + EventUtils.sendKey("RETURN", gDebugger); + }, + function () { + assertExpansion([false, false, false, false, false]); + EventUtils.sendKey("RETURN", gDebugger); + }, + function () { + assertExpansion([false, false, false, false, false]); + gEditor.focus(); + }, + function () { + assertExpansion([false, false, false, false, false]); + clearText(gSearchBox); + typeText(gSearchBox, "*"); + }, + function () { + assertExpansion([true, true, true, true, true]); + EventUtils.sendKey("RETURN", gDebugger); + }, + function () { + assertExpansion([true, true, true, true, true]); + EventUtils.sendKey("RETURN", gDebugger); + }, + function () { + assertExpansion([true, true, true, true, true]); + gEditor.focus(); + }, + function () { + assertExpansion([true, true, true, true, true]); + backspaceText(gSearchBox, 1); + }, + function () { + assertExpansion([true, true, true, true, true]); + EventUtils.sendKey("RETURN", gDebugger); + }, + function () { + assertExpansion([true, true, true, true, true]); + EventUtils.sendKey("RETURN", gDebugger); + }, + function () { + assertExpansion([true, true, true, true, true]); + gEditor.focus(); + }, + function () { + assertExpansion([true, true, true, true, true]); + } + ]; + + function assertExpansion(aFlags) { + is(localScope.expanded, aFlags[0], + "The localScope should " + (aFlags[0] ? "" : "not ") + + "be expanded at this point (" + step + ")."); + + is(withScope.expanded, aFlags[1], + "The withScope should " + (aFlags[1] ? "" : "not ") + + "be expanded at this point (" + step + ")."); + + is(functionScope.expanded, aFlags[2], + "The functionScope should " + (aFlags[2] ? "" : "not ") + + "be expanded at this point (" + step + ")."); + + is(globalLexicalScope.expanded, aFlags[3], + "The globalLexicalScope should " + (aFlags[3] ? "" : "not ") + + "be expanded at this point (" + step + ")."); + + is(globalScope.expanded, aFlags[4], + "The globalScope should " + (aFlags[4] ? "" : "not ") + + "be expanded at this point (" + step + ")."); + + step++; + } + + return promise.all(tests.map(f => f())); +} + +function prepareVariablesAndProperties() { + let deferred = promise.defer(); + + let localScope = gVariables.getScopeAtIndex(0); + let withScope = gVariables.getScopeAtIndex(1); + let functionScope = gVariables.getScopeAtIndex(2); + let globalLexicalScope = gVariables.getScopeAtIndex(3); + let globalScope = gVariables.getScopeAtIndex(4); + + is(localScope.expanded, true, + "The localScope should be expanded."); + is(withScope.expanded, false, + "The withScope should not be expanded yet."); + is(functionScope.expanded, false, + "The functionScope should not be expanded yet."); + is(globalLexicalScope.expanded, false, + "The globalLexicalScope should not be expanded yet."); + is(globalScope.expanded, false, + "The globalScope should not be expanded yet."); + + // Wait for only two events to be triggered, because the Function scope is + // an environment to which scope arguments and variables are already attached. + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 2).then(() => { + is(localScope.expanded, true, + "The localScope should now be expanded."); + is(withScope.expanded, true, + "The withScope should now be expanded."); + is(functionScope.expanded, true, + "The functionScope should now be expanded."); + is(globalLexicalScope.expanded, true, + "The globalLexicalScope should now be expanded."); + is(globalScope.expanded, true, + "The globalScope should now be expanded."); + + withScope.collapse(); + functionScope.collapse(); + globalLexicalScope.collapse(); + globalScope.collapse(); + + deferred.resolve(); + }); + + withScope.expand(); + functionScope.expand(); + globalLexicalScope.expand(); + globalScope.expand(); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gEditor = null; + gVariables = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-05.js new file mode 100644 index 000000000..4390955eb --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-05.js @@ -0,0 +1,254 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view correctly shows/hides nodes when various + * keyboard shortcuts are pressed in the debugger's searchbox. + */ + +const TAB_URL = EXAMPLE_URL + "doc_with-frame.html"; + +var gTab, gPanel, gDebugger; +var gVariables, gSearchBox; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(2); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVariables = gDebugger.DebuggerView.Variables; + gSearchBox = gDebugger.DebuggerView.Filtering._searchbox; + + // The first 'with' scope should be expanded by default, but the + // variables haven't been fetched yet. This is how 'with' scopes work. + promise.all([ + waitForCaretAndScopes(gPanel, 22), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES) + ]).then(prepareVariablesAndProperties) + .then(testVariablesAndPropertiesFiltering) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); +} + +function testVariablesAndPropertiesFiltering() { + let localScope = gVariables.getScopeAtIndex(0); + let withScope = gVariables.getScopeAtIndex(1); + let functionScope = gVariables.getScopeAtIndex(2); + let globalLexicalScope = gVariables.getScopeAtIndex(3); + let globalScope = gVariables.getScopeAtIndex(4); + let step = 0; + + let tests = [ + function () { + assertScopeExpansion([true, false, false, false, false]); + typeText(gSearchBox, "*arguments"); + }, + function () { + assertScopeExpansion([true, true, true, true, true]); + assertVariablesCountAtLeast([0, 0, 1, 0, 0]); + + is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"), + "arguments", "The arguments pseudoarray should be visible."); + is(functionScope.get("arguments").expanded, false, + "The arguments pseudoarray in functionScope should not be expanded."); + + backspaceText(gSearchBox, 6); + }, + function () { + assertScopeExpansion([true, true, true, true, true]); + assertVariablesCountAtLeast([0, 0, 1, 0, 1]); + + is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"), + "arguments", "The arguments pseudoarray should be visible."); + is(functionScope.get("arguments").expanded, false, + "The arguments pseudoarray in functionScope should not be expanded."); + + is(globalScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"), + "EventTarget", "The EventTarget object should be visible."); + is(globalScope.get("EventTarget").expanded, false, + "The EventTarget object in globalScope should not be expanded."); + + backspaceText(gSearchBox, 2); + }, + function () { + assertScopeExpansion([true, true, true, true, true]); + assertVariablesCountAtLeast([0, 1, 3, 0, 1]); + + is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"), + "aNumber", "The aNumber param should be visible."); + is(functionScope.get("aNumber").expanded, false, + "The aNumber param in functionScope should not be expanded."); + + is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[1].getAttribute("value"), + "a", "The a variable should be visible."); + is(functionScope.get("a").expanded, false, + "The a variable in functionScope should not be expanded."); + + is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[2].getAttribute("value"), + "arguments", "The arguments pseudoarray should be visible."); + is(functionScope.get("arguments").expanded, false, + "The arguments pseudoarray in functionScope should not be expanded."); + + backspaceText(gSearchBox, 1); + }, + function () { + assertScopeExpansion([true, true, true, true, true]); + assertVariablesCountAtLeast([4, 1, 3, 0, 1]); + + is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"), + "this", "The this reference should be visible."); + is(localScope.get("this").expanded, false, + "The this reference in localScope should not be expanded."); + + is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[1].getAttribute("value"), + "alpha", "The alpha variable should be visible."); + is(localScope.get("alpha").expanded, false, + "The alpha variable in localScope should not be expanded."); + + is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[2].getAttribute("value"), + "beta", "The beta variable should be visible."); + is(localScope.get("beta").expanded, false, + "The beta variable in localScope should not be expanded."); + + is(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[3].getAttribute("value"), + "__proto__", "The __proto__ reference should be visible."); + is(localScope.get("__proto__").expanded, false, + "The __proto__ reference in localScope should not be expanded."); + + is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[0].getAttribute("value"), + "aNumber", "The aNumber param should be visible."); + is(functionScope.get("aNumber").expanded, false, + "The aNumber param in functionScope should not be expanded."); + + is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[1].getAttribute("value"), + "a", "The a variable should be visible."); + is(functionScope.get("a").expanded, false, + "The a variable in functionScope should not be expanded."); + + is(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched]) > .title > .name")[2].getAttribute("value"), + "arguments", "The arguments pseudoarray should be visible."); + is(functionScope.get("arguments").expanded, false, + "The arguments pseudoarray in functionScope should not be expanded."); + } + ]; + + function assertScopeExpansion(aFlags) { + is(localScope.expanded, aFlags[0], + "The localScope should " + (aFlags[0] ? "" : "not ") + + "be expanded at this point (" + step + ")."); + + is(withScope.expanded, aFlags[1], + "The withScope should " + (aFlags[1] ? "" : "not ") + + "be expanded at this point (" + step + ")."); + + is(functionScope.expanded, aFlags[2], + "The functionScope should " + (aFlags[2] ? "" : "not ") + + "be expanded at this point (" + step + ")."); + + is(globalLexicalScope.expanded, aFlags[3], + "The globalLexicalScope should " + (aFlags[3] ? "" : "not ") + + "be expanded at this point (" + step + ")."); + + is(globalScope.expanded, aFlags[4], + "The globalScope should " + (aFlags[4] ? "" : "not ") + + "be expanded at this point (" + step + ")."); + } + + function assertVariablesCountAtLeast(aCounts) { + ok(localScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length >= aCounts[0], + "There should be " + aCounts[0] + + " variable displayed in the local scope (" + step + ")."); + + ok(withScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length >= aCounts[1], + "There should be " + aCounts[1] + + " variable displayed in the with scope (" + step + ")."); + + ok(functionScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length >= aCounts[2], + "There should be " + aCounts[2] + + " variable displayed in the function scope (" + step + ")."); + + ok(globalLexicalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length >= aCounts[3], + "There should be " + aCounts[3] + + " variable displayed in the global scope (" + step + ")."); + + ok(globalScope.target.querySelectorAll(".variables-view-variable:not([unmatched])").length >= aCounts[4], + "There should be " + aCounts[4] + + " variable displayed in the global scope (" + step + ")."); + + step++; + } + + return promise.all(tests.map(f => f())); +} + +function prepareVariablesAndProperties() { + let deferred = promise.defer(); + + let localScope = gVariables.getScopeAtIndex(0); + let withScope = gVariables.getScopeAtIndex(1); + let functionScope = gVariables.getScopeAtIndex(2); + let globalLexicalScope = gVariables.getScopeAtIndex(3); + let globalScope = gVariables.getScopeAtIndex(4); + + is(localScope.expanded, true, + "The localScope should be expanded."); + is(withScope.expanded, false, + "The withScope should not be expanded yet."); + is(functionScope.expanded, false, + "The functionScope should not be expanded yet."); + is(globalLexicalScope.expanded, false, + "The globalScope should not be expanded yet."); + is(globalScope.expanded, false, + "The globalScope should not be expanded yet."); + + // Wait for only two events to be triggered, because the Function scope is + // an environment to which scope arguments and variables are already attached. + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 2).then(() => { + is(localScope.expanded, true, + "The localScope should now be expanded."); + is(withScope.expanded, true, + "The withScope should now be expanded."); + is(functionScope.expanded, true, + "The functionScope should now be expanded."); + is(globalLexicalScope.expanded, true, + "The globalScope should now be expanded."); + is(globalScope.expanded, true, + "The globalScope should now be expanded."); + + withScope.collapse(); + functionScope.collapse(); + globalLexicalScope.collapse(); + globalScope.collapse(); + + deferred.resolve(); + }); + + withScope.expand(); + functionScope.expand(); + globalLexicalScope.expand(); + globalScope.expand(); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVariables = null; + gSearchBox = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-pref.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-pref.js new file mode 100644 index 000000000..783fc8a23 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-pref.js @@ -0,0 +1,85 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view filter prefs work properly. + */ + +const TAB_URL = EXAMPLE_URL + "doc_with-frame.html"; + +var gTab, gPanel, gDebugger; +var gPrefs, gOptions, gVariables; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gPrefs = gDebugger.Prefs; + gOptions = gDebugger.DebuggerView.Options; + gVariables = gDebugger.DebuggerView.Variables; + + performTest(); + }); +} + +function performTest() { + ok(!gVariables._searchboxNode, + "There should not initially be a searchbox available in the variables view."); + ok(!gVariables._searchboxContainer, + "There should not initially be a searchbox container available in the variables view."); + ok(!gVariables._parent.parentNode.querySelector(".variables-view-searchinput"), + "The searchbox element should not be found."); + + is(gPrefs.variablesSearchboxVisible, false, + "The debugger searchbox should be preffed as hidden."); + isnot(gOptions._showVariablesFilterBoxItem.getAttribute("checked"), "true", + "The options menu item should not be checked."); + + gOptions._showVariablesFilterBoxItem.setAttribute("checked", "true"); + gOptions._toggleShowVariablesFilterBox(); + + ok(gVariables._searchboxNode, + "There should be a searchbox available in the variables view."); + ok(gVariables._searchboxContainer, + "There should be a searchbox container available in the variables view."); + ok(gVariables._parent.parentNode.querySelector(".variables-view-searchinput"), + "There searchbox element should be found."); + + is(gPrefs.variablesSearchboxVisible, true, + "The debugger searchbox should now be preffed as visible."); + is(gOptions._showVariablesFilterBoxItem.getAttribute("checked"), "true", + "The options menu item should now be checked."); + + gOptions._showVariablesFilterBoxItem.setAttribute("checked", "false"); + gOptions._toggleShowVariablesFilterBox(); + + ok(!gVariables._searchboxNode, + "There should not be a searchbox available in the variables view."); + ok(!gVariables._searchboxContainer, + "There should not be a searchbox container available in the variables view."); + ok(!gVariables._parent.parentNode.querySelector(".variables-view-searchinput"), + "There searchbox element should not be found."); + + is(gPrefs.variablesSearchboxVisible, false, + "The debugger searchbox should now be preffed as hidden."); + isnot(gOptions._showVariablesFilterBoxItem.getAttribute("checked"), "true", + "The options menu item should now be unchecked."); + + closeDebuggerAndFinish(gPanel); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gPrefs = null; + gOptions = null; + gVariables = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-searchbox.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-searchbox.js new file mode 100644 index 000000000..1030d105d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-searchbox.js @@ -0,0 +1,150 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view correctly shows the searchbox + * when prompted. + */ + +const TAB_URL = EXAMPLE_URL + "doc_with-frame.html"; + +var gTab, gPanel, gDebugger; +var gVariables; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVariables = gDebugger.DebuggerView.Variables; + + performTest(); + }); +} + +function performTest() { + // Step 1: the searchbox shouldn't initially be shown. + + ok(!gVariables._searchboxNode, + "There should not initially be a searchbox available in the variables view."); + ok(!gVariables._searchboxContainer, + "There should not initially be a searchbox container available in the variables view."); + ok(!gVariables._parent.parentNode.querySelector(".variables-view-searchinput"), + "The searchbox element should not be found."); + + // Step 2: test enable/disable cycles. + + gVariables._enableSearch(); + ok(gVariables._searchboxNode, + "There should be a searchbox available after enabling."); + ok(gVariables._searchboxContainer, + "There should be a searchbox container available after enabling."); + ok(gVariables._searchboxContainer.hidden, + "The searchbox container should be hidden at this point."); + ok(gVariables._parent.parentNode.querySelector(".variables-view-searchinput"), + "The searchbox element should be found."); + + gVariables._disableSearch(); + ok(!gVariables._searchboxNode, + "There shouldn't be a searchbox available after disabling."); + ok(!gVariables._searchboxContainer, + "There shouldn't be a searchbox container available after disabling."); + ok(!gVariables._parent.parentNode.querySelector(".variables-view-searchinput"), + "The searchbox element should not be found."); + + // Step 3: add a placeholder while the searchbox is hidden. + + var placeholder = "not freshly squeezed mango juice"; + + gVariables.searchPlaceholder = placeholder; + is(gVariables.searchPlaceholder, placeholder, + "The placeholder getter didn't return the expected string"); + + // Step 4: enable search and check the placeholder. + + gVariables._enableSearch(); + ok(gVariables._searchboxNode, + "There should be a searchbox available after enabling."); + ok(gVariables._searchboxContainer, + "There should be a searchbox container available after enabling."); + ok(gVariables._searchboxContainer.hidden, + "The searchbox container should be hidden at this point."); + ok(gVariables._parent.parentNode.querySelector(".variables-view-searchinput"), + "The searchbox element should be found."); + + is(gVariables._searchboxNode.getAttribute("placeholder"), + placeholder, "There correct placeholder should be applied to the searchbox."); + + // Step 5: add a placeholder while the searchbox is visible and check wether + // it has been immediatey applied. + + var placeholder = "freshly squeezed mango juice"; + + gVariables.searchPlaceholder = placeholder; + is(gVariables.searchPlaceholder, placeholder, + "The placeholder getter didn't return the expected string"); + + is(gVariables._searchboxNode.getAttribute("placeholder"), + placeholder, "There correct placeholder should be applied to the searchbox."); + + // Step 4: disable, enable, then test the placeholder. + + gVariables._disableSearch(); + ok(!gVariables._searchboxNode, + "There shouldn't be a searchbox available after disabling again."); + ok(!gVariables._searchboxContainer, + "There shouldn't be a searchbox container available after disabling again."); + ok(!gVariables._parent.parentNode.querySelector(".variables-view-searchinput"), + "The searchbox element should not be found."); + + gVariables._enableSearch(); + ok(gVariables._searchboxNode, + "There should be a searchbox available after enabling again."); + ok(gVariables._searchboxContainer, + "There should be a searchbox container available after enabling again."); + ok(gVariables._searchboxContainer.hidden, + "The searchbox container should be hidden at this point."); + ok(gVariables._parent.parentNode.querySelector(".variables-view-searchinput"), + "The searchbox element should be found."); + + is(gVariables._searchboxNode.getAttribute("placeholder"), + placeholder, "There correct placeholder should be applied to the searchbox again."); + + // Step 5: alternate disable, enable, then test the placeholder. + + gVariables.searchEnabled = false; + ok(!gVariables._searchboxNode, + "There shouldn't be a searchbox available after disabling again."); + ok(!gVariables._searchboxContainer, + "There shouldn't be a searchbox container available after disabling again."); + ok(!gVariables._parent.parentNode.querySelector(".variables-view-searchinput"), + "The searchbox element should not be found."); + + gVariables.searchEnabled = true; + ok(gVariables._searchboxNode, + "There should be a searchbox available after enabling again."); + ok(gVariables._searchboxContainer, + "There should be a searchbox container available after enabling again."); + ok(gVariables._searchboxContainer.hidden, + "The searchbox container should be hidden at this point."); + ok(gVariables._parent.parentNode.querySelector(".variables-view-searchinput"), + "The searchbox element should be found."); + + is(gVariables._searchboxNode.getAttribute("placeholder"), + placeholder, "There correct placeholder should be applied to the searchbox again."); + + closeDebuggerAndFinish(gPanel); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVariables = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-01.js new file mode 100644 index 000000000..03dcf1440 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-01.js @@ -0,0 +1,270 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view correctly displays the properties + * of objects when debugger is paused. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +var gTab, gPanel, gDebugger; +var gVariables; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(2); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVariables = gDebugger.DebuggerView.Variables; + + waitForCaretAndScopes(gPanel, 24) + .then(initialChecks) + .then(testExpandVariables) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); +} + +function initialChecks() { + let scopeNodes = gDebugger.document.querySelectorAll(".variables-view-scope"); + is(scopeNodes.length, 3, + "There should be 3 scopes available."); + + ok(scopeNodes[0].querySelector(".name").getAttribute("value").includes("[test]"), + "The local scope should be properly identified."); + ok(scopeNodes[1].querySelector(".name").getAttribute("value").includes("Block"), + "The global lexical scope should be properly identified."); + ok(scopeNodes[2].querySelector(".name").getAttribute("value").includes("[Window]"), + "The global scope should be properly identified."); + + is(gVariables.getScopeAtIndex(0).target, scopeNodes[0], + "getScopeAtIndex(0) didn't return the expected scope."); + is(gVariables.getScopeAtIndex(1).target, scopeNodes[1], + "getScopeAtIndex(1) didn't return the expected scope."); + is(gVariables.getScopeAtIndex(2).target, scopeNodes[2], + "getScopeAtIndex(2) didn't return the expected scope."); + + is(gVariables.getItemForNode(scopeNodes[0]).target, scopeNodes[0], + "getItemForNode([0]) didn't return the expected scope."); + is(gVariables.getItemForNode(scopeNodes[1]).target, scopeNodes[1], + "getItemForNode([1]) didn't return the expected scope."); + is(gVariables.getItemForNode(scopeNodes[2]).target, scopeNodes[2], + "getItemForNode([2]) didn't return the expected scope."); + + is(gVariables.getItemForNode(scopeNodes[0]).expanded, true, + "The local scope should be expanded by default."); + is(gVariables.getItemForNode(scopeNodes[1]).expanded, false, + "The global lexical scope should not be collapsed by default."); + is(gVariables.getItemForNode(scopeNodes[2]).expanded, false, + "The global scope should not be collapsed by default."); +} + +function testExpandVariables() { + let deferred = promise.defer(); + + let localScope = gVariables.getScopeAtIndex(0); + let localEnums = localScope.target.querySelector(".variables-view-element-details.enum").childNodes; + + let thisVar = gVariables.getItemForNode(localEnums[0]); + let argsVar = gVariables.getItemForNode(localEnums[8]); + let cVar = gVariables.getItemForNode(localEnums[10]); + + is(thisVar.target.querySelector(".name").getAttribute("value"), "this", + "Should have the right property name for 'this'."); + is(argsVar.target.querySelector(".name").getAttribute("value"), "arguments", + "Should have the right property name for 'arguments'."); + is(cVar.target.querySelector(".name").getAttribute("value"), "c", + "Should have the right property name for 'c'."); + + is(thisVar.expanded, false, + "The thisVar should not be expanded at this point."); + is(argsVar.expanded, false, + "The argsVar should not be expanded at this point."); + is(cVar.expanded, false, + "The cVar should not be expanded at this point."); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 3).then(() => { + is(thisVar.get("window").target.querySelector(".name").getAttribute("value"), "window", + "Should have the right property name for 'window'."); + is(thisVar.get("window").target.querySelector(".value").getAttribute("value"), + "Window \u2192 doc_frame-parameters.html", + "Should have the right property value for 'window'."); + ok(thisVar.get("window").target.querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'window'."); + + is(thisVar.get("document").target.querySelector(".name").getAttribute("value"), "document", + "Should have the right property name for 'document'."); + is(thisVar.get("document").target.querySelector(".value").getAttribute("value"), + "HTMLDocument \u2192 doc_frame-parameters.html", + "Should have the right property value for 'document'."); + ok(thisVar.get("document").target.querySelector(".value").className.includes("token-domnode"), + "Should have the right token class for 'document'."); + + let argsProps = argsVar.target.querySelectorAll(".variables-view-property"); + is(argsProps.length, 8, + "The 'arguments' variable should contain 5 enumerable and 3 non-enumerable properties"); + + is(argsProps[0].querySelector(".name").getAttribute("value"), "0", + "Should have the right property name for '0'."); + is(argsProps[0].querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for '0'."); + ok(argsProps[0].querySelector(".value").className.includes("token-other"), + "Should have the right token class for '0'."); + + is(argsProps[1].querySelector(".name").getAttribute("value"), "1", + "Should have the right property name for '1'."); + is(argsProps[1].querySelector(".value").getAttribute("value"), "\"beta\"", + "Should have the right property value for '1'."); + ok(argsProps[1].querySelector(".value").className.includes("token-string"), + "Should have the right token class for '1'."); + + is(argsProps[2].querySelector(".name").getAttribute("value"), "2", + "Should have the right property name for '2'."); + is(argsProps[2].querySelector(".value").getAttribute("value"), "3", + "Should have the right property name for '2'."); + ok(argsProps[2].querySelector(".value").className.includes("token-number"), + "Should have the right token class for '2'."); + + is(argsProps[3].querySelector(".name").getAttribute("value"), "3", + "Should have the right property name for '3'."); + is(argsProps[3].querySelector(".value").getAttribute("value"), "false", + "Should have the right property value for '3'."); + ok(argsProps[3].querySelector(".value").className.includes("token-boolean"), + "Should have the right token class for '3'."); + + is(argsProps[4].querySelector(".name").getAttribute("value"), "4", + "Should have the right property name for '4'."); + is(argsProps[4].querySelector(".value").getAttribute("value"), "null", + "Should have the right property name for '4'."); + ok(argsProps[4].querySelector(".value").className.includes("token-null"), + "Should have the right token class for '4'."); + + is(gVariables.getItemForNode(argsProps[0]).target, + argsVar.target.querySelectorAll(".variables-view-property")[0], + "getItemForNode([0]) didn't return the expected property."); + + is(gVariables.getItemForNode(argsProps[1]).target, + argsVar.target.querySelectorAll(".variables-view-property")[1], + "getItemForNode([1]) didn't return the expected property."); + + is(gVariables.getItemForNode(argsProps[2]).target, + argsVar.target.querySelectorAll(".variables-view-property")[2], + "getItemForNode([2]) didn't return the expected property."); + + is(argsVar.find(argsProps[0]).target, + argsVar.target.querySelectorAll(".variables-view-property")[0], + "find([0]) didn't return the expected property."); + + is(argsVar.find(argsProps[1]).target, + argsVar.target.querySelectorAll(".variables-view-property")[1], + "find([1]) didn't return the expected property."); + + is(argsVar.find(argsProps[2]).target, + argsVar.target.querySelectorAll(".variables-view-property")[2], + "find([2]) didn't return the expected property."); + + let cProps = cVar.target.querySelectorAll(".variables-view-property"); + is(cProps.length, 7, + "The 'c' variable should contain 6 enumerable and 1 non-enumerable properties"); + + is(cProps[0].querySelector(".name").getAttribute("value"), "a", + "Should have the right property name for 'a'."); + is(cProps[0].querySelector(".value").getAttribute("value"), "1", + "Should have the right property value for 'a'."); + ok(cProps[0].querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'a'."); + + is(cProps[1].querySelector(".name").getAttribute("value"), "b", + "Should have the right property name for 'b'."); + is(cProps[1].querySelector(".value").getAttribute("value"), "\"beta\"", + "Should have the right property value for 'b'."); + ok(cProps[1].querySelector(".value").className.includes("token-string"), + "Should have the right token class for 'b'."); + + is(cProps[2].querySelector(".name").getAttribute("value"), "c", + "Should have the right property name for 'c'."); + is(cProps[2].querySelector(".value").getAttribute("value"), "3", + "Should have the right property value for 'c'."); + ok(cProps[2].querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'c'."); + + is(cProps[3].querySelector(".name").getAttribute("value"), "d", + "Should have the right property name for 'd'."); + is(cProps[3].querySelector(".value").getAttribute("value"), "false", + "Should have the right property value for 'd'."); + ok(cProps[3].querySelector(".value").className.includes("token-boolean"), + "Should have the right token class for 'd'."); + + is(cProps[4].querySelector(".name").getAttribute("value"), "e", + "Should have the right property name for 'e'."); + is(cProps[4].querySelector(".value").getAttribute("value"), "null", + "Should have the right property value for 'e'."); + ok(cProps[4].querySelector(".value").className.includes("token-null"), + "Should have the right token class for 'e'."); + + is(cProps[5].querySelector(".name").getAttribute("value"), "f", + "Should have the right property name for 'f'."); + is(cProps[5].querySelector(".value").getAttribute("value"), "undefined", + "Should have the right property value for 'f'."); + ok(cProps[5].querySelector(".value").className.includes("token-undefined"), + "Should have the right token class for 'f'."); + + is(gVariables.getItemForNode(cProps[0]).target, + cVar.target.querySelectorAll(".variables-view-property")[0], + "getItemForNode([0]) didn't return the expected property."); + + is(gVariables.getItemForNode(cProps[1]).target, + cVar.target.querySelectorAll(".variables-view-property")[1], + "getItemForNode([1]) didn't return the expected property."); + + is(gVariables.getItemForNode(cProps[2]).target, + cVar.target.querySelectorAll(".variables-view-property")[2], + "getItemForNode([2]) didn't return the expected property."); + + is(cVar.find(cProps[0]).target, + cVar.target.querySelectorAll(".variables-view-property")[0], + "find([0]) didn't return the expected property."); + + is(cVar.find(cProps[1]).target, + cVar.target.querySelectorAll(".variables-view-property")[1], + "find([1]) didn't return the expected property."); + + is(cVar.find(cProps[2]).target, + cVar.target.querySelectorAll(".variables-view-property")[2], + "find([2]) didn't return the expected property."); + }); + + // Expand the 'this', 'arguments' and 'c' variables view nodes. This causes + // their properties to be retrieved and displayed. + thisVar.expand(); + argsVar.expand(); + cVar.expand(); + + is(thisVar.expanded, true, + "The thisVar should be immediately marked as expanded."); + is(argsVar.expanded, true, + "The argsVar should be immediately marked as expanded."); + is(cVar.expanded, true, + "The cVar should be immediately marked as expanded."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVariables = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-02.js new file mode 100644 index 000000000..61bfb5275 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-02.js @@ -0,0 +1,552 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view displays the right variables and + * properties when debugger is paused. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +var gTab, gPanel, gDebugger; +var gVariables; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(2); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVariables = gDebugger.DebuggerView.Variables; + + waitForCaretAndScopes(gPanel, 24) + .then(testScopeVariables) + .then(testArgumentsProperties) + .then(testSimpleObject) + .then(testComplexObject) + .then(testArgumentObject) + .then(testInnerArgumentObject) + .then(testGetterSetterObject) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); +} + +function testScopeVariables() { + let localScope = gVariables.getScopeAtIndex(0); + is(localScope.expanded, true, + "The local scope should be expanded by default."); + + let localEnums = localScope.target.querySelector(".variables-view-element-details.enum").childNodes; + let localNonEnums = localScope.target.querySelector(".variables-view-element-details.nonenum").childNodes; + + is(localEnums.length, 12, + "The local scope should contain all the created enumerable elements."); + is(localNonEnums.length, 0, + "The local scope should contain all the created non-enumerable elements."); + + is(localEnums[0].querySelector(".name").getAttribute("value"), "this", + "Should have the right property name for 'this'."); + is(localEnums[0].querySelector(".value").getAttribute("value"), + "Window \u2192 doc_frame-parameters.html", + "Should have the right property value for 'this'."); + ok(localEnums[0].querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'this'."); + + is(localEnums[1].querySelector(".name").getAttribute("value"), "aArg", + "Should have the right property name for 'aArg'."); + is(localEnums[1].querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for 'aArg'."); + ok(localEnums[1].querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'aArg'."); + + is(localEnums[2].querySelector(".name").getAttribute("value"), "bArg", + "Should have the right property name for 'bArg'."); + is(localEnums[2].querySelector(".value").getAttribute("value"), "\"beta\"", + "Should have the right property value for 'bArg'."); + ok(localEnums[2].querySelector(".value").className.includes("token-string"), + "Should have the right token class for 'bArg'."); + + is(localEnums[3].querySelector(".name").getAttribute("value"), "cArg", + "Should have the right property name for 'cArg'."); + is(localEnums[3].querySelector(".value").getAttribute("value"), "3", + "Should have the right property value for 'cArg'."); + ok(localEnums[3].querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'cArg'."); + + is(localEnums[4].querySelector(".name").getAttribute("value"), "dArg", + "Should have the right property name for 'dArg'."); + is(localEnums[4].querySelector(".value").getAttribute("value"), "false", + "Should have the right property value for 'dArg'."); + ok(localEnums[4].querySelector(".value").className.includes("token-boolean"), + "Should have the right token class for 'dArg'."); + + is(localEnums[5].querySelector(".name").getAttribute("value"), "eArg", + "Should have the right property name for 'eArg'."); + is(localEnums[5].querySelector(".value").getAttribute("value"), "null", + "Should have the right property value for 'eArg'."); + ok(localEnums[5].querySelector(".value").className.includes("token-null"), + "Should have the right token class for 'eArg'."); + + is(localEnums[6].querySelector(".name").getAttribute("value"), "fArg", + "Should have the right property name for 'fArg'."); + is(localEnums[6].querySelector(".value").getAttribute("value"), "undefined", + "Should have the right property value for 'fArg'."); + ok(localEnums[6].querySelector(".value").className.includes("token-undefined"), + "Should have the right token class for 'fArg'."); + + is(localEnums[7].querySelector(".name").getAttribute("value"), "a", + "Should have the right property name for 'a'."); + is(localEnums[7].querySelector(".value").getAttribute("value"), "1", + "Should have the right property value for 'a'."); + ok(localEnums[7].querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'a'."); + + is(localEnums[8].querySelector(".name").getAttribute("value"), "arguments", + "Should have the right property name for 'arguments'."); + is(localEnums[8].querySelector(".value").getAttribute("value"), "Arguments", + "Should have the right property value for 'arguments'."); + ok(localEnums[8].querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'arguments'."); + + is(localEnums[9].querySelector(".name").getAttribute("value"), "b", + "Should have the right property name for 'b'."); + is(localEnums[9].querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for 'b'."); + ok(localEnums[9].querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'b'."); + + is(localEnums[10].querySelector(".name").getAttribute("value"), "c", + "Should have the right property name for 'c'."); + is(localEnums[10].querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for 'c'."); + ok(localEnums[10].querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'c'."); + + is(localEnums[11].querySelector(".name").getAttribute("value"), "myVar", + "Should have the right property name for 'myVar'."); + is(localEnums[11].querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for 'myVar'."); + ok(localEnums[11].querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'myVar'."); +} + +function testArgumentsProperties() { + let deferred = promise.defer(); + + let argsVar = gVariables.getScopeAtIndex(0).get("arguments"); + is(argsVar.expanded, false, + "The 'arguments' variable should not be expanded by default."); + + let argsEnums = argsVar.target.querySelector(".variables-view-element-details.enum").childNodes; + let argsNonEnums = argsVar.target.querySelector(".variables-view-element-details.nonenum").childNodes; + + gDebugger.once(gDebugger.EVENTS.FETCHED_PROPERTIES, () => { + is(argsEnums.length, 5, + "The 'arguments' variable should contain all the created enumerable elements."); + is(argsNonEnums.length, 3, + "The 'arguments' variable should contain all the created non-enumerable elements."); + + is(argsEnums[0].querySelector(".name").getAttribute("value"), "0", + "Should have the right property name for '0'."); + is(argsEnums[0].querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for '0'."); + ok(argsEnums[0].querySelector(".value").className.includes("token-other"), + "Should have the right token class for '0'."); + + is(argsEnums[1].querySelector(".name").getAttribute("value"), "1", + "Should have the right property name for '1'."); + is(argsEnums[1].querySelector(".value").getAttribute("value"), "\"beta\"", + "Should have the right property value for '1'."); + ok(argsEnums[1].querySelector(".value").className.includes("token-string"), + "Should have the right token class for '1'."); + + is(argsEnums[2].querySelector(".name").getAttribute("value"), "2", + "Should have the right property name for '2'."); + is(argsEnums[2].querySelector(".value").getAttribute("value"), "3", + "Should have the right property name for '2'."); + ok(argsEnums[2].querySelector(".value").className.includes("token-number"), + "Should have the right token class for '2'."); + + is(argsEnums[3].querySelector(".name").getAttribute("value"), "3", + "Should have the right property name for '3'."); + is(argsEnums[3].querySelector(".value").getAttribute("value"), "false", + "Should have the right property value for '3'."); + ok(argsEnums[3].querySelector(".value").className.includes("token-boolean"), + "Should have the right token class for '3'."); + + is(argsEnums[4].querySelector(".name").getAttribute("value"), "4", + "Should have the right property name for '4'."); + is(argsEnums[4].querySelector(".value").getAttribute("value"), "null", + "Should have the right property name for '4'."); + ok(argsEnums[4].querySelector(".value").className.includes("token-null"), + "Should have the right token class for '4'."); + + is(argsNonEnums[0].querySelector(".name").getAttribute("value"), "callee", + "Should have the right property name for 'callee'."); + is(argsNonEnums[0].querySelector(".value").getAttribute("value"), + "test(aArg,bArg,cArg,dArg,eArg,fArg)", + "Should have the right property name for 'callee'."); + ok(argsNonEnums[0].querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'callee'."); + + is(argsNonEnums[1].querySelector(".name").getAttribute("value"), "length", + "Should have the right property name for 'length'."); + is(argsNonEnums[1].querySelector(".value").getAttribute("value"), "5", + "Should have the right property value for 'length'."); + ok(argsNonEnums[1].querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'length'."); + + is(argsNonEnums[2].querySelector(".name").getAttribute("value"), "__proto__", + "Should have the right property name for '__proto__'."); + is(argsNonEnums[2].querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for '__proto__'."); + ok(argsNonEnums[2].querySelector(".value").className.includes("token-other"), + "Should have the right token class for '__proto__'."); + + deferred.resolve(); + }); + + argsVar.expand(); + return deferred.promise; +} + +function testSimpleObject() { + let deferred = promise.defer(); + + let bVar = gVariables.getScopeAtIndex(0).get("b"); + is(bVar.expanded, false, + "The 'b' variable should not be expanded by default."); + + let bEnums = bVar.target.querySelector(".variables-view-element-details.enum").childNodes; + let bNonEnums = bVar.target.querySelector(".variables-view-element-details.nonenum").childNodes; + + gDebugger.once(gDebugger.EVENTS.FETCHED_PROPERTIES, () => { + is(bEnums.length, 1, + "The 'b' variable should contain all the created enumerable elements."); + is(bNonEnums.length, 1, + "The 'b' variable should contain all the created non-enumerable elements."); + + is(bEnums[0].querySelector(".name").getAttribute("value"), "a", + "Should have the right property name for 'a'."); + is(bEnums[0].querySelector(".value").getAttribute("value"), "1", + "Should have the right property value for 'a'."); + ok(bEnums[0].querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'a'."); + + is(bNonEnums[0].querySelector(".name").getAttribute("value"), "__proto__", + "Should have the right property name for '__proto__'."); + is(bNonEnums[0].querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for '__proto__'."); + ok(bNonEnums[0].querySelector(".value").className.includes("token-other"), + "Should have the right token class for '__proto__'."); + + deferred.resolve(); + }); + + bVar.expand(); + return deferred.promise; +} + +function testComplexObject() { + let deferred = promise.defer(); + + let cVar = gVariables.getScopeAtIndex(0).get("c"); + is(cVar.expanded, false, + "The 'c' variable should not be expanded by default."); + + let cEnums = cVar.target.querySelector(".variables-view-element-details.enum").childNodes; + let cNonEnums = cVar.target.querySelector(".variables-view-element-details.nonenum").childNodes; + + gDebugger.once(gDebugger.EVENTS.FETCHED_PROPERTIES, () => { + is(cEnums.length, 6, + "The 'c' variable should contain all the created enumerable elements."); + is(cNonEnums.length, 1, + "The 'c' variable should contain all the created non-enumerable elements."); + + is(cEnums[0].querySelector(".name").getAttribute("value"), "a", + "Should have the right property name for 'a'."); + is(cEnums[0].querySelector(".value").getAttribute("value"), "1", + "Should have the right property value for 'a'."); + ok(cEnums[0].querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'a'."); + + is(cEnums[1].querySelector(".name").getAttribute("value"), "b", + "Should have the right property name for 'b'."); + is(cEnums[1].querySelector(".value").getAttribute("value"), "\"beta\"", + "Should have the right property value for 'b'."); + ok(cEnums[1].querySelector(".value").className.includes("token-string"), + "Should have the right token class for 'b'."); + + is(cEnums[2].querySelector(".name").getAttribute("value"), "c", + "Should have the right property name for 'c'."); + is(cEnums[2].querySelector(".value").getAttribute("value"), "3", + "Should have the right property value for 'c'."); + ok(cEnums[2].querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'c'."); + + is(cEnums[3].querySelector(".name").getAttribute("value"), "d", + "Should have the right property name for 'd'."); + is(cEnums[3].querySelector(".value").getAttribute("value"), "false", + "Should have the right property value for 'd'."); + ok(cEnums[3].querySelector(".value").className.includes("token-boolean"), + "Should have the right token class for 'd'."); + + is(cEnums[4].querySelector(".name").getAttribute("value"), "e", + "Should have the right property name for 'e'."); + is(cEnums[4].querySelector(".value").getAttribute("value"), "null", + "Should have the right property value for 'e'."); + ok(cEnums[4].querySelector(".value").className.includes("token-null"), + "Should have the right token class for 'e'."); + + is(cEnums[5].querySelector(".name").getAttribute("value"), "f", + "Should have the right property name for 'f'."); + is(cEnums[5].querySelector(".value").getAttribute("value"), "undefined", + "Should have the right property value for 'f'."); + ok(cEnums[5].querySelector(".value").className.includes("token-undefined"), + "Should have the right token class for 'f'."); + + is(cNonEnums[0].querySelector(".name").getAttribute("value"), "__proto__", + "Should have the right property name for '__proto__'."); + is(cNonEnums[0].querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for '__proto__'."); + ok(cNonEnums[0].querySelector(".value").className.includes("token-other"), + "Should have the right token class for '__proto__'."); + + deferred.resolve(); + }); + + cVar.expand(); + return deferred.promise; +} + +function testArgumentObject() { + let deferred = promise.defer(); + + let argVar = gVariables.getScopeAtIndex(0).get("aArg"); + is(argVar.expanded, false, + "The 'aArg' variable should not be expanded by default."); + + let argEnums = argVar.target.querySelector(".variables-view-element-details.enum").childNodes; + let argNonEnums = argVar.target.querySelector(".variables-view-element-details.nonenum").childNodes; + + gDebugger.once(gDebugger.EVENTS.FETCHED_PROPERTIES, () => { + is(argEnums.length, 6, + "The 'aArg' variable should contain all the created enumerable elements."); + is(argNonEnums.length, 1, + "The 'aArg' variable should contain all the created non-enumerable elements."); + + is(argEnums[0].querySelector(".name").getAttribute("value"), "a", + "Should have the right property name for 'a'."); + is(argEnums[0].querySelector(".value").getAttribute("value"), "1", + "Should have the right property value for 'a'."); + ok(argEnums[0].querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'a'."); + + is(argEnums[1].querySelector(".name").getAttribute("value"), "b", + "Should have the right property name for 'b'."); + is(argEnums[1].querySelector(".value").getAttribute("value"), "\"beta\"", + "Should have the right property value for 'b'."); + ok(argEnums[1].querySelector(".value").className.includes("token-string"), + "Should have the right token class for 'b'."); + + is(argEnums[2].querySelector(".name").getAttribute("value"), "c", + "Should have the right property name for 'c'."); + is(argEnums[2].querySelector(".value").getAttribute("value"), "3", + "Should have the right property value for 'c'."); + ok(argEnums[2].querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'c'."); + + is(argEnums[3].querySelector(".name").getAttribute("value"), "d", + "Should have the right property name for 'd'."); + is(argEnums[3].querySelector(".value").getAttribute("value"), "false", + "Should have the right property value for 'd'."); + ok(argEnums[3].querySelector(".value").className.includes("token-boolean"), + "Should have the right token class for 'd'."); + + is(argEnums[4].querySelector(".name").getAttribute("value"), "e", + "Should have the right property name for 'e'."); + is(argEnums[4].querySelector(".value").getAttribute("value"), "null", + "Should have the right property value for 'e'."); + ok(argEnums[4].querySelector(".value").className.includes("token-null"), + "Should have the right token class for 'e'."); + + is(argEnums[5].querySelector(".name").getAttribute("value"), "f", + "Should have the right property name for 'f'."); + is(argEnums[5].querySelector(".value").getAttribute("value"), "undefined", + "Should have the right property value for 'f'."); + ok(argEnums[5].querySelector(".value").className.includes("token-undefined"), + "Should have the right token class for 'f'."); + + is(argNonEnums[0].querySelector(".name").getAttribute("value"), "__proto__", + "Should have the right property name for '__proto__'."); + is(argNonEnums[0].querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for '__proto__'."); + ok(argNonEnums[0].querySelector(".value").className.includes("token-other"), + "Should have the right token class for '__proto__'."); + + deferred.resolve(); + }); + + argVar.expand(); + return deferred.promise; +} + +function testInnerArgumentObject() { + let deferred = promise.defer(); + + let argProp = gVariables.getScopeAtIndex(0).get("arguments").get("0"); + is(argProp.expanded, false, + "The 'arguments[0]' property should not be expanded by default."); + + let argEnums = argProp.target.querySelector(".variables-view-element-details.enum").childNodes; + let argNonEnums = argProp.target.querySelector(".variables-view-element-details.nonenum").childNodes; + + gDebugger.once(gDebugger.EVENTS.FETCHED_PROPERTIES, () => { + is(argEnums.length, 6, + "The 'arguments[0]' property should contain all the created enumerable elements."); + is(argNonEnums.length, 1, + "The 'arguments[0]' property should contain all the created non-enumerable elements."); + + is(argEnums[0].querySelector(".name").getAttribute("value"), "a", + "Should have the right property name for 'a'."); + is(argEnums[0].querySelector(".value").getAttribute("value"), "1", + "Should have the right property value for 'a'."); + ok(argEnums[0].querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'a'."); + + is(argEnums[1].querySelector(".name").getAttribute("value"), "b", + "Should have the right property name for 'b'."); + is(argEnums[1].querySelector(".value").getAttribute("value"), "\"beta\"", + "Should have the right property value for 'b'."); + ok(argEnums[1].querySelector(".value").className.includes("token-string"), + "Should have the right token class for 'b'."); + + is(argEnums[2].querySelector(".name").getAttribute("value"), "c", + "Should have the right property name for 'c'."); + is(argEnums[2].querySelector(".value").getAttribute("value"), "3", + "Should have the right property value for 'c'."); + ok(argEnums[2].querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'c'."); + + is(argEnums[3].querySelector(".name").getAttribute("value"), "d", + "Should have the right property name for 'd'."); + is(argEnums[3].querySelector(".value").getAttribute("value"), "false", + "Should have the right property value for 'd'."); + ok(argEnums[3].querySelector(".value").className.includes("token-boolean"), + "Should have the right token class for 'd'."); + + is(argEnums[4].querySelector(".name").getAttribute("value"), "e", + "Should have the right property name for 'e'."); + is(argEnums[4].querySelector(".value").getAttribute("value"), "null", + "Should have the right property value for 'e'."); + ok(argEnums[4].querySelector(".value").className.includes("token-null"), + "Should have the right token class for 'e'."); + + is(argEnums[5].querySelector(".name").getAttribute("value"), "f", + "Should have the right property name for 'f'."); + is(argEnums[5].querySelector(".value").getAttribute("value"), "undefined", + "Should have the right property value for 'f'."); + ok(argEnums[5].querySelector(".value").className.includes("token-undefined"), + "Should have the right token class for 'f'."); + + is(argNonEnums[0].querySelector(".name").getAttribute("value"), "__proto__", + "Should have the right property name for '__proto__'."); + is(argNonEnums[0].querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for '__proto__'."); + ok(argNonEnums[0].querySelector(".value").className.includes("token-other"), + "Should have the right token class for '__proto__'."); + + deferred.resolve(); + }); + + argProp.expand(); + return deferred.promise; +} + +function testGetterSetterObject() { + let deferred = promise.defer(); + + let myVar = gVariables.getScopeAtIndex(0).get("myVar"); + is(myVar.expanded, false, + "The myVar variable should not be expanded by default."); + + let myVarEnums = myVar.target.querySelector(".variables-view-element-details.enum").childNodes; + let myVarNonEnums = myVar.target.querySelector(".variables-view-element-details.nonenum").childNodes; + + gDebugger.once(gDebugger.EVENTS.FETCHED_PROPERTIES, () => { + is(myVarEnums.length, 2, + "The myVar should contain all the created enumerable elements."); + is(myVarNonEnums.length, 1, + "The myVar should contain all the created non-enumerable elements."); + + is(myVarEnums[0].querySelector(".name").getAttribute("value"), "_prop", + "Should have the right property name for '_prop'."); + is(myVarEnums[0].querySelector(".value").getAttribute("value"), "42", + "Should have the right property value for '_prop'."); + ok(myVarEnums[0].querySelector(".value").className.includes("token-number"), + "Should have the right token class for '_prop'."); + + is(myVarEnums[1].querySelector(".name").getAttribute("value"), "prop", + "Should have the right property name for 'prop'."); + is(myVarEnums[1].querySelector(".value").getAttribute("value"), "", + "Should have the right property value for 'prop'."); + ok(!myVarEnums[1].querySelector(".value").className.includes("token"), + "Should have no token class for 'prop'."); + + is(myVarNonEnums[0].querySelector(".name").getAttribute("value"), "__proto__", + "Should have the right property name for '__proto__'."); + is(myVarNonEnums[0].querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for '__proto__'."); + ok(myVarNonEnums[0].querySelector(".value").className.includes("token-other"), + "Should have the right token class for '__proto__'."); + + let propEnums = myVarEnums[1].querySelector(".variables-view-element-details.enum").childNodes; + let propNonEnums = myVarEnums[1].querySelector(".variables-view-element-details.nonenum").childNodes; + + is(propEnums.length, 0, + "The propEnums should contain all the created enumerable elements."); + is(propNonEnums.length, 2, + "The propEnums should contain all the created non-enumerable elements."); + + is(propNonEnums[0].querySelector(".name").getAttribute("value"), "get", + "Should have the right property name for 'get'."); + is(propNonEnums[0].querySelector(".value").getAttribute("value"), + "get prop()", + "Should have the right property value for 'get'."); + ok(propNonEnums[0].querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'get'."); + + is(propNonEnums[1].querySelector(".name").getAttribute("value"), "set", + "Should have the right property name for 'set'."); + is(propNonEnums[1].querySelector(".value").getAttribute("value"), + "set prop(val)", + "Should have the right property value for 'set'."); + ok(propNonEnums[1].querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'set'."); + + deferred.resolve(); + }); + + myVar.expand(); + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVariables = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-03.js new file mode 100644 index 000000000..04f23ac7c --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-03.js @@ -0,0 +1,157 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view displays the right variables and + * properties in the global scope when debugger is paused. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +var gTab, gPanel, gDebugger; +var gVariables; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(2); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVariables = gDebugger.DebuggerView.Variables; + + waitForCaretAndScopes(gPanel, 24) + .then(expandGlobalScope) + .then(testGlobalScope) + .then(expandWindowVariable) + .then(testWindowVariable) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); +} + +function expandGlobalScope() { + let deferred = promise.defer(); + + let globalScope = gVariables.getScopeAtIndex(2); + is(globalScope.expanded, false, + "The global scope should not be expanded by default."); + + gDebugger.once(gDebugger.EVENTS.FETCHED_VARIABLES, deferred.resolve); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + globalScope.target.querySelector(".name"), + gDebugger); + + return deferred.promise; +} + +function testGlobalScope() { + let globalScope = gVariables.getScopeAtIndex(2); + is(globalScope.expanded, true, + "The global scope should now be expanded."); + + is(globalScope.get("InstallTrigger").target.querySelector(".name").getAttribute("value"), "InstallTrigger", + "Should have the right property name for 'InstallTrigger'."); + is(globalScope.get("InstallTrigger").target.querySelector(".value").getAttribute("value"), "InstallTriggerImpl", + "Should have the right property value for 'InstallTrigger'."); + + is(globalScope.get("SpecialPowers").target.querySelector(".name").getAttribute("value"), "SpecialPowers", + "Should have the right property name for 'SpecialPowers'."); + is(globalScope.get("SpecialPowers").target.querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for 'SpecialPowers'."); + + is(globalScope.get("window").target.querySelector(".name").getAttribute("value"), "window", + "Should have the right property name for 'window'."); + is(globalScope.get("window").target.querySelector(".value").getAttribute("value"), + "Window \u2192 doc_frame-parameters.html", + "Should have the right property value for 'window'."); + + is(globalScope.get("document").target.querySelector(".name").getAttribute("value"), "document", + "Should have the right property name for 'document'."); + is(globalScope.get("document").target.querySelector(".value").getAttribute("value"), + "HTMLDocument \u2192 doc_frame-parameters.html", + "Should have the right property value for 'document'."); + + is(globalScope.get("undefined").target.querySelector(".name").getAttribute("value"), "undefined", + "Should have the right property name for 'undefined'."); + is(globalScope.get("undefined").target.querySelector(".value").getAttribute("value"), "undefined", + "Should have the right property value for 'undefined'."); + + is(globalScope.get("undefined").target.querySelector(".enum").childNodes.length, 0, + "Should have no child enumerable properties for 'undefined'."); + is(globalScope.get("undefined").target.querySelector(".nonenum").childNodes.length, 0, + "Should have no child non-enumerable properties for 'undefined'."); +} + +function expandWindowVariable() { + let deferred = promise.defer(); + + let windowVar = gVariables.getScopeAtIndex(2).get("window"); + is(windowVar.expanded, false, + "The window variable should not be expanded by default."); + + gDebugger.once(gDebugger.EVENTS.FETCHED_PROPERTIES, deferred.resolve); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + windowVar.target.querySelector(".name"), + gDebugger); + + return deferred.promise; +} + +function testWindowVariable() { + let windowVar = gVariables.getScopeAtIndex(2).get("window"); + is(windowVar.expanded, true, + "The window variable should now be expanded."); + + is(windowVar.get("InstallTrigger").target.querySelector(".name").getAttribute("value"), "InstallTrigger", + "Should have the right property name for 'InstallTrigger'."); + is(windowVar.get("InstallTrigger").target.querySelector(".value").getAttribute("value"), "InstallTriggerImpl", + "Should have the right property value for 'InstallTrigger'."); + + is(windowVar.get("SpecialPowers").target.querySelector(".name").getAttribute("value"), "SpecialPowers", + "Should have the right property name for 'SpecialPowers'."); + is(windowVar.get("SpecialPowers").target.querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for 'SpecialPowers'."); + + is(windowVar.get("window").target.querySelector(".name").getAttribute("value"), "window", + "Should have the right property name for 'window'."); + is(windowVar.get("window").target.querySelector(".value").getAttribute("value"), + "Window \u2192 doc_frame-parameters.html", + "Should have the right property value for 'window'."); + + is(windowVar.get("document").target.querySelector(".name").getAttribute("value"), "document", + "Should have the right property name for 'document'."); + is(windowVar.get("document").target.querySelector(".value").getAttribute("value"), + "HTMLDocument \u2192 doc_frame-parameters.html", + "Should have the right property value for 'document'."); + + is(windowVar.get("undefined").target.querySelector(".name").getAttribute("value"), "undefined", + "Should have the right property name for 'undefined'."); + is(windowVar.get("undefined").target.querySelector(".value").getAttribute("value"), "undefined", + "Should have the right property value for 'undefined'."); + + is(windowVar.get("undefined").target.querySelector(".enum").childNodes.length, 0, + "Should have no child enumerable properties for 'undefined'."); + is(windowVar.get("undefined").target.querySelector(".nonenum").childNodes.length, 0, + "Should have no child non-enumerable properties for 'undefined'."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVariables = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-with.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-with.js new file mode 100644 index 000000000..dc98e2d9b --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-with.js @@ -0,0 +1,212 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view is correctly populated in 'with' frames. + */ + +const TAB_URL = EXAMPLE_URL + "doc_with-frame.html"; + +var gTab, gPanel, gDebugger; +var gVariables; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVariables = gDebugger.DebuggerView.Variables; + + // The first 'with' scope should be expanded by default, but the + // variables haven't been fetched yet. This is how 'with' scopes work. + promise.all([ + waitForCaretAndScopes(gPanel, 22), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES) + ]).then(testFirstWithScope) + .then(expandSecondWithScope) + .then(testSecondWithScope) + .then(expandFunctionScope) + .then(testFunctionScope) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); +} + +function testFirstWithScope() { + let firstWithScope = gVariables.getScopeAtIndex(0); + is(firstWithScope.expanded, true, + "The first 'with' scope should be expanded by default."); + ok(firstWithScope.target.querySelector(".name").getAttribute("value").includes("[Object]"), + "The first 'with' scope should be properly identified."); + + let withEnums = firstWithScope._enum.childNodes; + let withNonEnums = firstWithScope._nonenum.childNodes; + + is(withEnums.length, 3, + "The first 'with' scope should contain all the created enumerable elements."); + is(withNonEnums.length, 1, + "The first 'with' scope should contain all the created non-enumerable elements."); + + is(withEnums[0].querySelector(".name").getAttribute("value"), "this", + "Should have the right property name for 'this'."); + is(withEnums[0].querySelector(".value").getAttribute("value"), + "Window \u2192 doc_with-frame.html", + "Should have the right property value for 'this'."); + ok(withEnums[0].querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'this'."); + + is(withEnums[1].querySelector(".name").getAttribute("value"), "alpha", + "Should have the right property name for 'alpha'."); + is(withEnums[1].querySelector(".value").getAttribute("value"), "1", + "Should have the right property value for 'alpha'."); + ok(withEnums[1].querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'alpha'."); + + is(withEnums[2].querySelector(".name").getAttribute("value"), "beta", + "Should have the right property name for 'beta'."); + is(withEnums[2].querySelector(".value").getAttribute("value"), "2", + "Should have the right property value for 'beta'."); + ok(withEnums[2].querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'beta'."); + + is(withNonEnums[0].querySelector(".name").getAttribute("value"), "__proto__", + "Should have the right property name for '__proto__'."); + is(withNonEnums[0].querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for '__proto__'."); + ok(withNonEnums[0].querySelector(".value").className.includes("token-other"), + "Should have the right token class for '__proto__'."); +} + +function expandSecondWithScope() { + let deferred = promise.defer(); + + let secondWithScope = gVariables.getScopeAtIndex(1); + is(secondWithScope.expanded, false, + "The second 'with' scope should not be expanded by default."); + + gDebugger.once(gDebugger.EVENTS.FETCHED_VARIABLES, deferred.resolve); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + secondWithScope.target.querySelector(".name"), + gDebugger); + + return deferred.promise; +} + +function testSecondWithScope() { + let secondWithScope = gVariables.getScopeAtIndex(1); + is(secondWithScope.expanded, true, + "The second 'with' scope should now be expanded."); + ok(secondWithScope.target.querySelector(".name").getAttribute("value").includes("[Math]"), + "The second 'with' scope should be properly identified."); + + let withEnums = secondWithScope._enum.childNodes; + let withNonEnums = secondWithScope._nonenum.childNodes; + + is(withEnums.length, 0, + "The second 'with' scope should contain all the created enumerable elements."); + isnot(withNonEnums.length, 0, + "The second 'with' scope should contain all the created non-enumerable elements."); + + is(secondWithScope.get("E").target.querySelector(".name").getAttribute("value"), "E", + "Should have the right property name for 'E'."); + is(secondWithScope.get("E").target.querySelector(".value").getAttribute("value"), "2.718281828459045", + "Should have the right property value for 'E'."); + ok(secondWithScope.get("E").target.querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'E'."); + + is(secondWithScope.get("PI").target.querySelector(".name").getAttribute("value"), "PI", + "Should have the right property name for 'PI'."); + is(secondWithScope.get("PI").target.querySelector(".value").getAttribute("value"), "3.141592653589793", + "Should have the right property value for 'PI'."); + ok(secondWithScope.get("PI").target.querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'PI'."); + + is(secondWithScope.get("random").target.querySelector(".name").getAttribute("value"), "random", + "Should have the right property name for 'random'."); + is(secondWithScope.get("random").target.querySelector(".value").getAttribute("value"), "random()", + "Should have the right property value for 'random'."); + ok(secondWithScope.get("random").target.querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'random'."); + + is(secondWithScope.get("__proto__").target.querySelector(".name").getAttribute("value"), "__proto__", + "Should have the right property name for '__proto__'."); + is(secondWithScope.get("__proto__").target.querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for '__proto__'."); + ok(secondWithScope.get("__proto__").target.querySelector(".value").className.includes("token-other"), + "Should have the right token class for '__proto__'."); +} + +function expandFunctionScope() { + let funcScope = gVariables.getScopeAtIndex(2); + is(funcScope.expanded, false, + "The function scope shouldn't be expanded by default, but the " + + "variables have been already fetched. This is how local scopes work."); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + funcScope.target.querySelector(".name"), + gDebugger); + + return promise.resolve(null); +} + +function testFunctionScope() { + let funcScope = gVariables.getScopeAtIndex(2); + is(funcScope.expanded, true, + "The function scope should now be expanded."); + ok(funcScope.target.querySelector(".name").getAttribute("value").includes("[test]"), + "The function scope should be properly identified."); + + let funcEnums = funcScope._enum.childNodes; + let funcNonEnums = funcScope._nonenum.childNodes; + + is(funcEnums.length, 6, + "The function scope should contain all the created enumerable elements."); + is(funcNonEnums.length, 0, + "The function scope should contain all the created non-enumerable elements."); + + is(funcScope.get("aNumber").target.querySelector(".name").getAttribute("value"), "aNumber", + "Should have the right property name for 'aNumber'."); + is(funcScope.get("aNumber").target.querySelector(".value").getAttribute("value"), "10", + "Should have the right property value for 'aNumber'."); + ok(funcScope.get("aNumber").target.querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'aNumber'."); + + is(funcScope.get("a").target.querySelector(".name").getAttribute("value"), "a", + "Should have the right property name for 'a'."); + is(funcScope.get("a").target.querySelector(".value").getAttribute("value"), "314.1592653589793", + "Should have the right property value for 'a'."); + ok(funcScope.get("a").target.querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'a'."); + + is(funcScope.get("r").target.querySelector(".name").getAttribute("value"), "r", + "Should have the right property name for 'r'."); + is(funcScope.get("r").target.querySelector(".value").getAttribute("value"), "10", + "Should have the right property value for 'r'."); + ok(funcScope.get("r").target.querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'r'."); + + is(funcScope.get("foo").target.querySelector(".name").getAttribute("value"), "foo", + "Should have the right property name for 'foo'."); + is(funcScope.get("foo").target.querySelector(".value").getAttribute("value"), "6.283185307179586", + "Should have the right property value for 'foo'."); + ok(funcScope.get("foo").target.querySelector(".value").className.includes("token-number"), + "Should have the right token class for 'foo'."); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVariables = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frozen-sealed-nonext.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frozen-sealed-nonext.js new file mode 100644 index 000000000..736deeba1 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frozen-sealed-nonext.js @@ -0,0 +1,93 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This test checks that we properly set the frozen, sealed, and non-extensbile + * attributes on variables so that the F/S/N is shown in the variables view. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +var gTab, gPanel, gDebugger; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + + prepareTest(); + }); +} + +function prepareTest() { + gDebugger.once(gDebugger.EVENTS.FETCHED_SCOPES, runTest); + + evalInTab(gTab, "(" + function () { + var frozen = Object.freeze({}); + var sealed = Object.seal({}); + var nonExtensible = Object.preventExtensions({}); + var extensible = {}; + var string = "foo bar baz"; + + debugger; + } + "())"); +} + +function runTest() { + let hasNoneTester = function (aVariable) { + ok(!aVariable.hasAttribute("frozen"), + "The variable should not be frozen."); + ok(!aVariable.hasAttribute("sealed"), + "The variable should not be sealed."); + ok(!aVariable.hasAttribute("non-extensible"), + "The variable should be extensible."); + }; + + let testers = { + frozen: function (aVariable) { + ok(aVariable.hasAttribute("frozen"), + "The variable should be frozen."); + }, + sealed: function (aVariable) { + ok(aVariable.hasAttribute("sealed"), + "The variable should be sealed."); + }, + nonExtensible: function (aVariable) { + ok(aVariable.hasAttribute("non-extensible"), + "The variable should be non-extensible."); + }, + extensible: hasNoneTester, + string: hasNoneTester, + arguments: hasNoneTester, + this: hasNoneTester + }; + + let variables = gDebugger.document.querySelectorAll(".variable-or-property"); + + for (let variable of variables) { + let name = variable.querySelector(".name").getAttribute("value"); + let tester = testers[name]; + delete testers[name]; + + ok(tester, "We should have a tester for the '" + name + "' variable."); + tester(variable); + } + + is(Object.keys(testers).length, 0, + "We should have run and removed all the testers."); + + resumeDebuggerThenCloseAndFinish(gPanel); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-hide-non-enums.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-hide-non-enums.js new file mode 100644 index 000000000..8094cfdc2 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-hide-non-enums.js @@ -0,0 +1,111 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that non-enumerable variables and properties can be hidden + * in the variables view. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +var gTab, gPanel, gDebugger; + +function test() { + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + + waitForCaretAndScopes(gPanel, 14).then(performTest); + callInTab(gTab, "simpleCall"); + }); +} + +function performTest() { + let testScope = gDebugger.DebuggerView.Variables.addScope("test-scope"); + let testVar = testScope.addItem("foo"); + + testVar.addItems({ + foo: { + value: "bar", + enumerable: true + }, + bar: { + value: "foo", + enumerable: false + } + }); + + // Expand the scope and variable. + testScope.expand(); + testVar.expand(); + + // Expanding the non-enumerable container is synchronously dispatched + // on the main thread, so wait for the next tick. + executeSoon(() => { + let details = testVar._enum; + let nonenum = testVar._nonenum; + + is(details.childNodes.length, 1, + "There should be just one property in the .details container."); + ok(details.hasAttribute("open"), + ".details container should be visible."); + ok(nonenum.hasAttribute("open"), + ".nonenum container should be visible."); + is(nonenum.childNodes.length, 1, + "There should be just one property in the .nonenum container."); + + // Uncheck 'show hidden properties'. + gDebugger.DebuggerView.Options._showVariablesOnlyEnumItem.setAttribute("checked", "true"); + gDebugger.DebuggerView.Options._toggleShowVariablesOnlyEnum(); + + ok(details.hasAttribute("open"), + ".details container should stay visible."); + ok(!nonenum.hasAttribute("open"), + ".nonenum container should become hidden."); + + // Check 'show hidden properties'. + gDebugger.DebuggerView.Options._showVariablesOnlyEnumItem.setAttribute("checked", "false"); + gDebugger.DebuggerView.Options._toggleShowVariablesOnlyEnum(); + + ok(details.hasAttribute("open"), + ".details container should stay visible."); + ok(nonenum.hasAttribute("open"), + ".nonenum container should become visible."); + + // Collapse the variable. This is done on the current tick. + testVar.collapse(); + + ok(!details.hasAttribute("open"), + ".details container should be hidden."); + ok(!nonenum.hasAttribute("open"), + ".nonenum container should be hidden."); + + // Uncheck 'show hidden properties'. + gDebugger.DebuggerView.Options._showVariablesOnlyEnumItem.setAttribute("checked", "true"); + gDebugger.DebuggerView.Options._toggleShowVariablesOnlyEnum(); + + ok(!details.hasAttribute("open"), + ".details container should stay hidden."); + ok(!nonenum.hasAttribute("open"), + ".nonenum container should stay hidden."); + + // Check 'show hidden properties'. + gDebugger.DebuggerView.Options._showVariablesOnlyEnumItem.setAttribute("checked", "false"); + gDebugger.DebuggerView.Options._toggleShowVariablesOnlyEnum(); + + resumeDebuggerThenCloseAndFinish(gPanel); + }); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-large-array-buffer.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-large-array-buffer.js new file mode 100644 index 000000000..7ec1fe2f0 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-large-array-buffer.js @@ -0,0 +1,253 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view remains responsive when faced with + * huge ammounts of data. + */ + +"use strict"; + +const TAB_URL = EXAMPLE_URL + "doc_large-array-buffer.html"; +const {ELLIPSIS} = require("devtools/shared/l10n"); + + +var gTab, gPanel, gDebugger, gVariables; + +function test() { + // this test does a lot of work on large objects, default 45s is not enough + requestLongerTimeout(4); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVariables = gDebugger.DebuggerView.Variables; + + waitForCaretAndScopes(gPanel, 28, 1) + .then(() => performTests()) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, error => { + ok(false, "Got an error: " + error.message + "\n" + error.stack); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); +} + +const VARS_TO_TEST = [ + { + varName: "buffer", + stringified: "ArrayBuffer", + doNotExpand: true + }, + { + varName: "largeArray", + stringified: "Int8Array[10000]", + extraProps: [ + [ "buffer", "ArrayBuffer" ], + [ "byteLength", "10000" ], + [ "byteOffset", "0" ], + [ "length", "10000" ], + [ "__proto__", "Int8ArrayPrototype" ] + ] + }, + { + varName: "largeObject", + stringified: "Object[10000]", + extraProps: [ + [ "__proto__", "Object" ] + ] + }, + { + varName: "largeMap", + stringified: "Map[10000]", + hasEntries: true, + extraProps: [ + [ "size", "10000" ], + [ "__proto__", "Object" ] + ] + }, + { + varName: "largeSet", + stringified: "Set[10000]", + hasEntries: true, + extraProps: [ + [ "size", "10000" ], + [ "__proto__", "Object" ] + ] + } +]; + +const PAGE_RANGES = [ + [0, 2499], [2500, 4999], [5000, 7499], [7500, 9999] +]; + +function toPageNames(ranges) { + return ranges.map(([ from, to ]) => "[" + from + ELLIPSIS + to + "]"); +} + +function performTests() { + let localScope = gVariables.getScopeAtIndex(0); + + return promise.all(VARS_TO_TEST.map(spec => { + let { varName, stringified, doNotExpand } = spec; + + let variable = localScope.get(varName); + ok(variable, + `There should be a '${varName}' variable present in the scope.`); + + is(variable.target.querySelector(".name").getAttribute("value"), varName, + `Should have the right property name for '${varName}'.`); + is(variable.target.querySelector(".value").getAttribute("value"), stringified, + `Should have the right property value for '${varName}'.`); + ok(variable.target.querySelector(".value").className.includes("token-other"), + `Should have the right token class for '${varName}'.`); + + is(variable.expanded, false, + `The '${varName}' variable shouldn't be expanded.`); + + if (doNotExpand) { + return promise.resolve(); + } + + return variable.expand() + .then(() => verifyFirstLevel(variable, spec)); + })); +} + +// In objects and arrays, the sliced pages are at the top-level of +// the expanded object, but with Maps and Sets, we have to expand +// <entries> first and look there. +function getExpandedPages(variable, hasEntries) { + let expandedPages = promise.defer(); + if (hasEntries) { + let entries = variable.get("<entries>"); + ok(entries, "<entries> retrieved"); + entries.expand().then(() => expandedPages.resolve(entries)); + } else { + expandedPages.resolve(variable); + } + + return expandedPages.promise; +} + +function verifyFirstLevel(variable, spec) { + let { varName, hasEntries, extraProps } = spec; + + let enums = variable._enum.childNodes; + let nonEnums = variable._nonenum.childNodes; + + is(enums.length, hasEntries ? 1 : 4, + `The '${varName}' contains the right number of enumerable elements.`); + is(nonEnums.length, extraProps.length, + `The '${varName}' contains the right number of non-enumerable elements.`); + + // the sliced pages begin after <entries> row + let pagesOffset = hasEntries ? 1 : 0; + let expandedPages = getExpandedPages(variable, hasEntries); + + return expandedPages.then((pagesList) => { + toPageNames(PAGE_RANGES).forEach((pageName, i) => { + let index = i + pagesOffset; + + is(pagesList.target.querySelectorAll(".variables-view-property .name")[index].getAttribute("value"), + pageName, `The page #${i + 1} in the '${varName}' is named correctly.`); + is(pagesList.target.querySelectorAll(".variables-view-property .value")[index].getAttribute("value"), + "", `The page #${i + 1} in the '${varName}' should not have a corresponding value.`); + }); + }).then(() => { + extraProps.forEach(([ propName, propValue ], i) => { + // the extra props start after the 4 pages + let index = i + pagesOffset + 4; + + is(variable.target.querySelectorAll(".variables-view-property .name")[index].getAttribute("value"), + propName, `The other properties in '${varName}' are named correctly.`); + is(variable.target.querySelectorAll(".variables-view-property .value")[index].getAttribute("value"), + propValue, `The other properties in '${varName}' have the correct value.`); + }); + }).then(() => verifyNextLevels(variable, spec)); +} + +function verifyNextLevels(variable, spec) { + let { varName, hasEntries } = spec; + + // the entries are already expanded in verifyFirstLevel + let pagesList = hasEntries ? variable.get("<entries>") : variable; + + let lastPage = pagesList.get(toPageNames(PAGE_RANGES)[3]); + ok(lastPage, `The last page in the 1st level of '${varName}' was retrieved successfully.`); + + return lastPage.expand() + .then(() => verifyNextLevels2(lastPage, varName)); +} + +function verifyNextLevels2(lastPage1, varName) { + const PAGE_RANGES_IN_LAST_PAGE = [ + [7500, 8124], [8125, 8749], [8750, 9374], [9375, 9999] + ]; + + let pageEnums1 = lastPage1._enum.childNodes; + let pageNonEnums1 = lastPage1._nonenum.childNodes; + is(pageEnums1.length, 4, + `The last page in the 1st level of '${varName}' should contain all the created enumerable elements.`); + is(pageNonEnums1.length, 0, + `The last page in the 1st level of '${varName}' should not contain any non-enumerable elements.`); + + let pageNames = toPageNames(PAGE_RANGES_IN_LAST_PAGE); + pageNames.forEach((pageName, i) => { + is(lastPage1._enum.querySelectorAll(".variables-view-property .name")[i].getAttribute("value"), + pageName, `The page #${i + 1} in the 2nd level of '${varName}' is named correctly.`); + }); + + let lastPage2 = lastPage1.get(pageNames[3]); + ok(lastPage2, "The last page in the 2nd level was retrieved successfully."); + + return lastPage2.expand() + .then(() => verifyNextLevels3(lastPage2, varName)); +} + +function verifyNextLevels3(lastPage2, varName) { + let pageEnums2 = lastPage2._enum.childNodes; + let pageNonEnums2 = lastPage2._nonenum.childNodes; + is(pageEnums2.length, 625, + `The last page in the 3rd level of '${varName}' should contain all the created enumerable elements.`); + is(pageNonEnums2.length, 0, + `The last page in the 3rd level of '${varName}' shouldn't contain any non-enumerable elements.`); + + const LEAF_ITEMS = [ + [0, 9375, 624], + [1, 9376, 623], + [623, 9998, 1], + [624, 9999, 0] + ]; + + function expectedValue(name, value) { + switch (varName) { + case "largeArray": return 0; + case "largeObject": return value; + case "largeMap": return name + " \u2192 " + value; + case "largeSet": return value; + } + } + + LEAF_ITEMS.forEach(([index, name, value]) => { + is(lastPage2._enum.querySelectorAll(".variables-view-property .name")[index].getAttribute("value"), + name, `The properties in the leaf level of '${varName}' are named correctly.`); + is(lastPage2._enum.querySelectorAll(".variables-view-property .value")[index].getAttribute("value"), + expectedValue(name, value), `The properties in the leaf level of '${varName}' have the correct value.`); + }); +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVariables = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-map-set.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-map-set.js new file mode 100644 index 000000000..0c301c683 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-map-set.js @@ -0,0 +1,117 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Test that Map and Set and their Weak friends are displayed in variables view. + */ + +"use strict"; + +const TAB_URL = EXAMPLE_URL + "doc_map-set.html"; + +var test = Task.async(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + const [tab,, panel] = yield initDebugger(TAB_URL, options); + + const scopes = waitForCaretAndScopes(panel, 37); + callInTab(tab, "startTest"); + yield scopes; + + const variables = panel.panelWin.DebuggerView.Variables; + ok(variables, "Should get the variables view."); + + const scope = variables.getScopeAtIndex(0); + ok(scope, "Should get the current function's scope."); + + /* Test the maps */ + for (let varName of ["map", "weakMap"]) { + const mapVar = scope.get(varName); + ok(mapVar, `Retrieved the '${varName}' variable from the scope`); + + info(`Expanding '${varName}' variable`); + yield mapVar.expand(); + + const entries = mapVar.get("<entries>"); + ok(entries, `Retrieved the '${varName}' entries`); + + info(`Expanding '${varName}' entries`); + yield entries.expand(); + + // Check the entries. WeakMap returns its entries in a nondeterministic + // order, so we make our job easier by not testing the exact values. + let i = 0; + for (let [ name, entry ] of entries) { + is(name, i, `The '${varName}' entry's property name is correct`); + ok(entry.displayValue.startsWith("Object \u2192 "), + `The '${varName}' entry's property value is correct`); + yield entry.expand(); + + let key = entry.get("key"); + ok(key, `The '${varName}' entry has the 'key' property`); + yield key.expand(); + + let keyProperty = key.get("a"); + ok(keyProperty, + `The '${varName}' entry's 'key' has the correct property`); + + let value = entry.get("value"); + ok(value, `The '${varName}' entry has the 'value' property`); + + i++; + } + + is(i, 2, `The '${varName}' entry count is correct`); + + // Check the extra property on the object + let extraProp = mapVar.get("extraProp"); + ok(extraProp, `Retrieved the '${varName}' extraProp`); + is(extraProp.displayValue, "true", + `The '${varName}' extraProp's value is correct`); + } + + /* Test the sets */ + for (let varName of ["set", "weakSet"]) { + const setVar = scope.get(varName); + ok(setVar, `Retrieved the '${varName}' variable from the scope`); + + info(`Expanding '${varName}' variable`); + yield setVar.expand(); + + const entries = setVar.get("<entries>"); + ok(entries, `Retrieved the '${varName}' entries`); + + info(`Expanding '${varName}' entries`); + yield entries.expand(); + + // Check the entries. WeakSet returns its entries in a nondeterministic + // order, so we make our job easier by not testing the exact values. + let i = 0; + for (let [ name, entry ] of entries) { + is(name, i, `The '${varName}' entry's property name is correct`); + is(entry.displayValue, "Object", + `The '${varName}' entry's property value is correct`); + yield entry.expand(); + + let entryProperty = entry.get("a"); + ok(entryProperty, + `The '${varName}' entry's value has the correct property`); + + i++; + } + + is(i, 2, `The '${varName}' entry count is correct`); + + // Check the extra property on the object + let extraProp = setVar.get("extraProp"); + ok(extraProp, `Retrieved the '${varName}' extraProp`); + is(extraProp.displayValue, "true", + `The '${varName}' extraProp's value is correct`); + } + + resumeDebuggerThenCloseAndFinish(panel); +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-override-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-override-01.js new file mode 100644 index 000000000..f923d7f53 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-override-01.js @@ -0,0 +1,240 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that VariablesView methods responsible for styling variables + * as overridden work properly. + */ + +const TAB_URL = EXAMPLE_URL + "doc_scope-variable-2.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let events = win.EVENTS; + let variables = win.DebuggerView.Variables; + + callInTab(tab, "test"); + yield waitForCaretAndScopes(panel, 23); + + let firstScope = variables.getScopeAtIndex(0); + let secondScope = variables.getScopeAtIndex(1); + let thirdScope = variables.getScopeAtIndex(2); + let globalLexicalScope = variables.getScopeAtIndex(3); + let globalScope = variables.getScopeAtIndex(4); + + ok(firstScope, "The first scope is available."); + ok(secondScope, "The second scope is available."); + ok(thirdScope, "The third scope is available."); + ok(globalLexicalScope, "The global lexical scope is available."); + ok(globalScope, "The global scope is available."); + + is(firstScope.name, "Function scope [secondNest]", + "The first scope's name is correct."); + is(secondScope.name, "Function scope [firstNest]", + "The second scope's name is correct."); + is(thirdScope.name, "Function scope [test]", + "The third scope's name is correct."); + is(globalLexicalScope.name, "Block scope", + "The global lexical scope's name is correct."); + is(globalScope.name, "Global scope [Window]", + "The global scope's name is correct."); + + is(firstScope.expanded, true, + "The first scope's expansion state is correct."); + is(secondScope.expanded, false, + "The second scope's expansion state is correct."); + is(thirdScope.expanded, false, + "The third scope's expansion state is correct."); + is(globalLexicalScope.expanded, false, + "The global lexical scope's expansion state is correct."); + is(globalScope.expanded, false, + "The global scope's expansion state is correct."); + + is(firstScope._store.size, 3, + "The first scope should have all the variables available."); + is(secondScope._store.size, 0, + "The second scope should have no variables available yet."); + is(thirdScope._store.size, 0, + "The third scope should have no variables available yet."); + is(globalLexicalScope._store.size, 0, + "The global scope should have no variables available yet."); + is(globalScope._store.size, 0, + "The global scope should have no variables available yet."); + + // Test getOwnerScopeForVariableOrProperty with simple variables. + + let thisVar = firstScope.get("this"); + let thisOwner = variables.getOwnerScopeForVariableOrProperty(thisVar); + is(thisOwner, firstScope, + "The getOwnerScopeForVariableOrProperty method works properly (1)."); + + let someVar1 = firstScope.get("a"); + let someOwner1 = variables.getOwnerScopeForVariableOrProperty(someVar1); + is(someOwner1, firstScope, + "The getOwnerScopeForVariableOrProperty method works properly (2)."); + + // Test getOwnerScopeForVariableOrProperty with first-degree properties. + + let argsVar1 = firstScope.get("arguments"); + let fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES); + argsVar1.expand(); + yield fetched; + + let calleeProp1 = argsVar1.get("callee"); + let calleeOwner1 = variables.getOwnerScopeForVariableOrProperty(calleeProp1); + is(calleeOwner1, firstScope, + "The getOwnerScopeForVariableOrProperty method works properly (3)."); + + // Test getOwnerScopeForVariableOrProperty with second-degree properties. + + let protoVar1 = argsVar1.get("__proto__"); + fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES); + protoVar1.expand(); + yield fetched; + + let constrProp1 = protoVar1.get("constructor"); + let constrOwner1 = variables.getOwnerScopeForVariableOrProperty(constrProp1); + is(constrOwner1, firstScope, + "The getOwnerScopeForVariableOrProperty method works properly (4)."); + + // Test getOwnerScopeForVariableOrProperty with a simple variable + // from non-topmost scopes. + + // Only need to wait for a single FETCHED_VARIABLES event, just for the + // global scope, because the other local scopes already have the + // arguments and variables available as evironment bindings. + fetched = waitForDebuggerEvents(panel, events.FETCHED_VARIABLES); + secondScope.expand(); + thirdScope.expand(); + globalLexicalScope.expand(); + globalScope.expand(); + yield fetched; + + let someVar2 = secondScope.get("a"); + let someOwner2 = variables.getOwnerScopeForVariableOrProperty(someVar2); + is(someOwner2, secondScope, + "The getOwnerScopeForVariableOrProperty method works properly (5)."); + + let someVar3 = thirdScope.get("a"); + let someOwner3 = variables.getOwnerScopeForVariableOrProperty(someVar3); + is(someOwner3, thirdScope, + "The getOwnerScopeForVariableOrProperty method works properly (6)."); + + // Test getOwnerScopeForVariableOrProperty with first-degree properies + // from non-topmost scopes. + + let argsVar2 = secondScope.get("arguments"); + fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES); + argsVar2.expand(); + yield fetched; + + let calleeProp2 = argsVar2.get("callee"); + let calleeOwner2 = variables.getOwnerScopeForVariableOrProperty(calleeProp2); + is(calleeOwner2, secondScope, + "The getOwnerScopeForVariableOrProperty method works properly (7)."); + + let argsVar3 = thirdScope.get("arguments"); + fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES); + argsVar3.expand(); + yield fetched; + + let calleeProp3 = argsVar3.get("callee"); + let calleeOwner3 = variables.getOwnerScopeForVariableOrProperty(calleeProp3); + is(calleeOwner3, thirdScope, + "The getOwnerScopeForVariableOrProperty method works properly (8)."); + + // Test getOwnerScopeForVariableOrProperty with second-degree properties + // from non-topmost scopes. + + let protoVar2 = argsVar2.get("__proto__"); + fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES); + protoVar2.expand(); + yield fetched; + + let constrProp2 = protoVar2.get("constructor"); + let constrOwner2 = variables.getOwnerScopeForVariableOrProperty(constrProp2); + is(constrOwner2, secondScope, + "The getOwnerScopeForVariableOrProperty method works properly (9)."); + + let protoVar3 = argsVar3.get("__proto__"); + fetched = waitForDebuggerEvents(panel, events.FETCHED_PROPERTIES); + protoVar3.expand(); + yield fetched; + + let constrProp3 = protoVar3.get("constructor"); + let constrOwner3 = variables.getOwnerScopeForVariableOrProperty(constrProp3); + is(constrOwner3, thirdScope, + "The getOwnerScopeForVariableOrProperty method works properly (10)."); + + // Test getParentScopesForVariableOrProperty with simple variables. + + let varOwners1 = variables.getParentScopesForVariableOrProperty(someVar1); + let varOwners2 = variables.getParentScopesForVariableOrProperty(someVar2); + let varOwners3 = variables.getParentScopesForVariableOrProperty(someVar3); + + is(varOwners1.length, 0, + "There should be no owner scopes for the first variable."); + + is(varOwners2.length, 1, + "There should be one owner scope for the second variable."); + is(varOwners2[0], firstScope, + "The only owner scope for the second variable is correct."); + + is(varOwners3.length, 2, + "There should be two owner scopes for the third variable."); + is(varOwners3[0], firstScope, + "The first owner scope for the third variable is correct."); + is(varOwners3[1], secondScope, + "The second owner scope for the third variable is correct."); + + // Test getParentScopesForVariableOrProperty with first-degree properties. + + let propOwners1 = variables.getParentScopesForVariableOrProperty(calleeProp1); + let propOwners2 = variables.getParentScopesForVariableOrProperty(calleeProp2); + let propOwners3 = variables.getParentScopesForVariableOrProperty(calleeProp3); + + is(propOwners1.length, 0, + "There should be no owner scopes for the first property."); + + is(propOwners2.length, 1, + "There should be one owner scope for the second property."); + is(propOwners2[0], firstScope, + "The only owner scope for the second property is correct."); + + is(propOwners3.length, 2, + "There should be two owner scopes for the third property."); + is(propOwners3[0], firstScope, + "The first owner scope for the third property is correct."); + is(propOwners3[1], secondScope, + "The second owner scope for the third property is correct."); + + // Test getParentScopesForVariableOrProperty with second-degree properties. + + let secPropOwners1 = variables.getParentScopesForVariableOrProperty(constrProp1); + let secPropOwners2 = variables.getParentScopesForVariableOrProperty(constrProp2); + let secPropOwners3 = variables.getParentScopesForVariableOrProperty(constrProp3); + + is(secPropOwners1.length, 0, + "There should be no owner scopes for the first inner property."); + + is(secPropOwners2.length, 1, + "There should be one owner scope for the second inner property."); + is(secPropOwners2[0], firstScope, + "The only owner scope for the second inner property is correct."); + + is(secPropOwners3.length, 2, + "There should be two owner scopes for the third inner property."); + is(secPropOwners3[0], firstScope, + "The first owner scope for the third inner property is correct."); + is(secPropOwners3[1], secondScope, + "The second owner scope for the third inner property is correct."); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-override-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-override-02.js new file mode 100644 index 000000000..276efb665 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-override-02.js @@ -0,0 +1,75 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that overridden variables in the VariablesView are styled properly. + */ + +const TAB_URL = EXAMPLE_URL + "doc_scope-variable-2.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let events = win.EVENTS; + let variables = win.DebuggerView.Variables; + + // Wait for the hierarchy to be committed by the VariablesViewController. + let committedLocalScopeHierarchy = promise.defer(); + variables.oncommit = committedLocalScopeHierarchy.resolve; + + + let onCaretAndScopes = waitForCaretAndScopes(panel, 23); + callInTab(tab, "test"); + yield onCaretAndScopes; + yield committedLocalScopeHierarchy.promise; + + let firstScope = variables.getScopeAtIndex(0); + let secondScope = variables.getScopeAtIndex(1); + let thirdScope = variables.getScopeAtIndex(2); + + let someVar1 = firstScope.get("a"); + let argsVar1 = firstScope.get("arguments"); + + is(someVar1.target.hasAttribute("overridden"), false, + "The first 'a' variable should not be marked as being overridden."); + is(argsVar1.target.hasAttribute("overridden"), false, + "The first 'arguments' variable should not be marked as being overridden."); + + // Wait for the hierarchy to be committed by the VariablesViewController. + let committedSecondScopeHierarchy = promise.defer(); + variables.oncommit = committedSecondScopeHierarchy.resolve; + secondScope.expand(); + yield committedSecondScopeHierarchy.promise; + + let someVar2 = secondScope.get("a"); + let argsVar2 = secondScope.get("arguments"); + + is(someVar2.target.hasAttribute("overridden"), true, + "The second 'a' variable should be marked as being overridden."); + is(argsVar2.target.hasAttribute("overridden"), true, + "The second 'arguments' variable should be marked as being overridden."); + + // Wait for the hierarchy to be committed by the VariablesViewController. + let committedThirdScopeHierarchy = promise.defer(); + variables.oncommit = committedThirdScopeHierarchy.resolve; + thirdScope.expand(); + yield committedThirdScopeHierarchy.promise; + + let someVar3 = thirdScope.get("a"); + let argsVar3 = thirdScope.get("arguments"); + + is(someVar3.target.hasAttribute("overridden"), true, + "The third 'a' variable should be marked as being overridden."); + is(argsVar3.target.hasAttribute("overridden"), true, + "The third 'arguments' variable should be marked as being overridden."); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-01.js new file mode 100644 index 000000000..2e7244fad --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-01.js @@ -0,0 +1,67 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests opening the variable inspection popup on a variable which has a + * simple literal as the value. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let bubble = win.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + + bubble._ignoreLiterals = false; + + function verifyContents(textContent, className) { + is(tooltip.querySelectorAll(".variables-view-container").length, 0, + "There should be no variables view containers added to the tooltip."); + is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1, + "There should be a simple text node added to the tooltip instead."); + + is(tooltip.querySelector(".devtools-tooltip-simple-text").textContent, textContent, + "The inspected property's value is correct."); + ok(tooltip.querySelector(".devtools-tooltip-simple-text").className.includes(className), + "The inspected property's value is colorized correctly."); + } + + + let onCaretAndScopes = waitForCaretAndScopes(panel, 24); + callInTab(tab, "start"); + yield onCaretAndScopes; + + // Inspect variables. + yield openVarPopup(panel, { line: 15, ch: 12 }); + verifyContents("1", "token-number"); + + yield reopenVarPopup(panel, { line: 16, ch: 21 }); + verifyContents("1", "token-number"); + + yield reopenVarPopup(panel, { line: 17, ch: 21 }); + verifyContents("1", "token-number"); + + yield reopenVarPopup(panel, { line: 17, ch: 27 }); + verifyContents("\"beta\"", "token-string"); + + yield reopenVarPopup(panel, { line: 17, ch: 44 }); + verifyContents("false", "token-boolean"); + + yield reopenVarPopup(panel, { line: 17, ch: 54 }); + verifyContents("null", "token-null"); + + yield reopenVarPopup(panel, { line: 17, ch: 63 }); + verifyContents("undefined", "token-undefined"); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-02.js new file mode 100644 index 000000000..0f9843fc2 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-02.js @@ -0,0 +1,53 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests opening the variable inspection popup on a variable which has a + * a property accessible via getters and setters. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let bubble = win.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + + function verifyContents(textContent, className) { + is(tooltip.querySelectorAll(".variables-view-container").length, 0, + "There should be no variables view containers added to the tooltip."); + is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1, + "There should be a simple text node added to the tooltip instead."); + + is(tooltip.querySelector(".devtools-tooltip-simple-text").textContent, textContent, + "The inspected property's value is correct."); + ok(tooltip.querySelector(".devtools-tooltip-simple-text").className.includes(className), + "The inspected property's value is colorized correctly."); + } + + + let onCaretAndScopes = waitForCaretAndScopes(panel, 24); + callInTab(tab, "start"); + yield onCaretAndScopes; + + // Inspect properties. + yield openVarPopup(panel, { line: 19, ch: 10 }); + verifyContents("42", "token-number"); + + yield reopenVarPopup(panel, { line: 20, ch: 14 }); + verifyContents("42", "token-number"); + + yield reopenVarPopup(panel, { line: 21, ch: 14 }); + verifyContents("42", "token-number"); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-03.js new file mode 100644 index 000000000..2b59fcfc5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-03.js @@ -0,0 +1,49 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the inspected indentifier is highlighted. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let bubble = win.DebuggerView.VariableBubble; + + let onCaretAndScopes = waitForCaretAndScopes(panel, 24); + callInTab(tab, "start"); + yield onCaretAndScopes; + + // Inspect variable. + yield openVarPopup(panel, { line: 15, ch: 12 }); + + ok(bubble.contentsShown(), + "The variable should register as being shown."); + ok(!bubble._tooltip.isEmpty(), + "The variable inspection popup isn't empty."); + ok(bubble._markedText, + "There's some marked text in the editor."); + ok(bubble._markedText.clear, + "The marked text in the editor can be cleared."); + + yield hideVarPopup(panel); + + ok(!bubble.contentsShown(), + "The variable should register as being hidden."); + ok(bubble._tooltip.isEmpty(), + "The variable inspection popup is now empty."); + ok(!bubble._markedText, + "The marked text in the editor was removed."); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-04.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-04.js new file mode 100644 index 000000000..b175b7a50 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-04.js @@ -0,0 +1,38 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the variable inspection popup is hidden when the editor scrolls. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let bubble = win.DebuggerView.VariableBubble; + + let onCaretAndScopes = waitForCaretAndScopes(panel, 24); + callInTab(tab, "start"); + yield onCaretAndScopes; + + // Inspect variable. + yield openVarPopup(panel, { line: 15, ch: 12 }); + yield hideVarPopupByScrollingEditor(panel); + ok(true, "The variable inspection popup was hidden."); + + ok(bubble._tooltip.isEmpty(), + "The variable inspection popup is now empty."); + ok(!bubble._markedText, + "The marked text in the editor was removed."); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-05.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-05.js new file mode 100644 index 000000000..5f974efdf --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-05.js @@ -0,0 +1,57 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests opening the variable inspection popup on a variable which has a + * simple object as the value. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let bubble = win.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + + function verifyContents() { + is(tooltip.querySelectorAll(".variables-view-container").length, 1, + "There should be one variables view container added to the tooltip."); + + is(tooltip.querySelectorAll(".variables-view-scope[untitled]").length, 1, + "There should be one scope with no header displayed."); + is(tooltip.querySelectorAll(".variables-view-variable[untitled]").length, 1, + "There should be one variable with no header displayed."); + + is(tooltip.querySelectorAll(".variables-view-property").length, 2, + "There should be 2 properties displayed."); + + is(tooltip.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), "a", + "The first property's name is correct."); + is(tooltip.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"), "1", + "The first property's value is correct."); + + is(tooltip.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), "__proto__", + "The second property's name is correct."); + is(tooltip.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"), "Object", + "The second property's value is correct."); + } + + let onCaretAndScopes = waitForCaretAndScopes(panel, 24); + callInTab(tab, "start"); + yield onCaretAndScopes; + + // Inspect variable. + yield openVarPopup(panel, { line: 16, ch: 12 }, true); + verifyContents(); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-06.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-06.js new file mode 100644 index 000000000..2d0d5f06a --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-06.js @@ -0,0 +1,83 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests opening the variable inspection popup on a variable which has a + * complext object as the value. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +function test() { + requestLongerTimeout(2); + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let bubble = win.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + + function verifyContents() { + is(tooltip.querySelectorAll(".variables-view-container").length, 1, + "There should be one variables view container added to the tooltip."); + + is(tooltip.querySelectorAll(".variables-view-scope[untitled]").length, 1, + "There should be one scope with no header displayed."); + is(tooltip.querySelectorAll(".variables-view-variable[untitled]").length, 1, + "There should be one variable with no header displayed."); + + is(tooltip.querySelectorAll(".variables-view-property").length, 7, + "There should be 7 properties displayed."); + + is(tooltip.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"), "a", + "The first property's name is correct."); + is(tooltip.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"), "1", + "The first property's value is correct."); + + is(tooltip.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"), "b", + "The second property's name is correct."); + is(tooltip.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"), "\"beta\"", + "The second property's value is correct."); + + is(tooltip.querySelectorAll(".variables-view-property .name")[2].getAttribute("value"), "c", + "The third property's name is correct."); + is(tooltip.querySelectorAll(".variables-view-property .value")[2].getAttribute("value"), "3", + "The third property's value is correct."); + + is(tooltip.querySelectorAll(".variables-view-property .name")[3].getAttribute("value"), "d", + "The fourth property's name is correct."); + is(tooltip.querySelectorAll(".variables-view-property .value")[3].getAttribute("value"), "false", + "The fourth property's value is correct."); + + is(tooltip.querySelectorAll(".variables-view-property .name")[4].getAttribute("value"), "e", + "The fifth property's name is correct."); + is(tooltip.querySelectorAll(".variables-view-property .value")[4].getAttribute("value"), "null", + "The fifth property's value is correct."); + + is(tooltip.querySelectorAll(".variables-view-property .name")[5].getAttribute("value"), "f", + "The sixth property's name is correct."); + is(tooltip.querySelectorAll(".variables-view-property .value")[5].getAttribute("value"), "undefined", + "The sixth property's value is correct."); + + is(tooltip.querySelectorAll(".variables-view-property .name")[6].getAttribute("value"), "__proto__", + "The seventh property's name is correct."); + is(tooltip.querySelectorAll(".variables-view-property .value")[6].getAttribute("value"), "Object", + "The seventh property's value is correct."); + } + + let onCaretAndScopes = waitForCaretAndScopes(panel, 24); + callInTab(tab, "start"); + yield onCaretAndScopes; + + // Inspect variable. + yield openVarPopup(panel, { line: 17, ch: 12 }, true); + verifyContents(); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-07.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-07.js new file mode 100644 index 000000000..1340f14c6 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-07.js @@ -0,0 +1,70 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests the variable inspection popup behaves correctly when switching + * between simple and complex objects. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let bubble = win.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + + function verifySimpleContents(textContent, className) { + is(tooltip.querySelectorAll(".variables-view-container").length, 0, + "There should be no variables view container added to the tooltip."); + is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1, + "There should be one simple text node added to the tooltip."); + + is(tooltip.querySelector(".devtools-tooltip-simple-text").textContent, textContent, + "The inspected property's value is correct."); + ok(tooltip.querySelector(".devtools-tooltip-simple-text").className.includes(className), + "The inspected property's value is colorized correctly."); + } + + function verifyComplexContents(propertyCount) { + is(tooltip.querySelectorAll(".variables-view-container").length, 1, + "There should be one variables view container added to the tooltip."); + is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 0, + "There should be no simple text node added to the tooltip."); + + is(tooltip.querySelectorAll(".variables-view-scope[untitled]").length, 1, + "There should be one scope with no header displayed."); + is(tooltip.querySelectorAll(".variables-view-variable[untitled]").length, 1, + "There should be one variable with no header displayed."); + + ok(tooltip.querySelectorAll(".variables-view-property").length >= propertyCount, + "There should be some properties displayed."); + } + + let onCaretAndScopes = waitForCaretAndScopes(panel, 24); + callInTab(tab, "start"); + yield onCaretAndScopes; + + // Inspect variables. + yield openVarPopup(panel, { line: 15, ch: 12 }); + verifySimpleContents("1", "token-number"); + + yield reopenVarPopup(panel, { line: 16, ch: 12 }, true); + verifyComplexContents(2); + + yield reopenVarPopup(panel, { line: 19, ch: 10 }); + verifySimpleContents("42", "token-number"); + + yield reopenVarPopup(panel, { line: 31, ch: 10 }, true); + verifyComplexContents(100); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-08.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-08.js new file mode 100644 index 000000000..d3ef69e7e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-08.js @@ -0,0 +1,75 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests opening inspecting variables works across scopes. + */ + +const TAB_URL = EXAMPLE_URL + "doc_scope-variable.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let events = win.EVENTS; + let editor = win.DebuggerView.editor; + let frames = win.DebuggerView.StackFrames; + let bubble = win.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + + function verifyContents(textContent, className) { + is(tooltip.querySelectorAll(".variables-view-container").length, 0, + "There should be no variables view containers added to the tooltip."); + is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1, + "There should be a simple text node added to the tooltip instead."); + + is(tooltip.querySelector(".devtools-tooltip-simple-text").textContent, textContent, + "The inspected property's value is correct."); + ok(tooltip.querySelector(".devtools-tooltip-simple-text").className.includes(className), + "The inspected property's value is colorized correctly."); + } + + function checkView(selectedFrame, caretLine) { + is(win.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(frames.itemCount, 2, + "Should have two frames."); + is(frames.selectedDepth, selectedFrame, + "The correct frame is selected in the widget."); + ok(isCaretPos(panel, caretLine), + "Editor caret location is correct."); + } + + let onCaretAndScopes = waitForCaretAndScopes(panel, 20); + callInTab(tab, "test"); + yield onCaretAndScopes; + + checkView(0, 20); + + // Inspect variable in topmost frame. + yield openVarPopup(panel, { line: 18, ch: 12 }); + verifyContents("\"second scope\"", "token-string"); + checkView(0, 20); + + // Hide the popup and change the frame. + yield hideVarPopup(panel); + + let updatedFrame = waitForDebuggerEvents(panel, events.FETCHED_SCOPES); + frames.selectedDepth = 1; + yield updatedFrame; + checkView(1, 15); + + // Inspect variable in oldest frame. + yield openVarPopup(panel, { line: 13, ch: 12 }); + verifyContents("\"first scope\"", "token-string"); + checkView(1, 15); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-09.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-09.js new file mode 100644 index 000000000..41824cb76 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-09.js @@ -0,0 +1,39 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests opening inspecting variables works across scopes. + */ + +const TAB_URL = EXAMPLE_URL + "doc_scope-variable-3.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let bubble = win.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + + let onCaretAndScopes = waitForCaretAndScopes(panel, 15); + callInTab(tab, "test"); + yield onCaretAndScopes; + + yield openVarPopup(panel, { line: 12, ch: 10 }); + ok(true, "The variable inspection popup was shown for the real variable."); + + once(tooltip, "popupshown").then(() => { + ok(false, "The variable inspection popup shouldn't have been opened."); + }); + + reopenVarPopup(panel, { line: 18, ch: 10 }); + yield waitForTime(1000); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-10.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-10.js new file mode 100644 index 000000000..15dd02c44 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-10.js @@ -0,0 +1,67 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Makes sure the source editor's scroll location doesn't change when + * a variable inspection popup is opened and a watch expression is + * also evaluated at the same time. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let events = win.EVENTS; + let editor = win.DebuggerView.editor; + let editorContainer = win.document.getElementById("editor"); + let bubble = win.DebuggerView.VariableBubble; + let expressions = win.DebuggerView.WatchExpressions; + let tooltip = bubble._tooltip.panel; + + let onCaretAndScopes = waitForCaretAndScopes(panel, 24); + callInTab(tab, "start"); + yield onCaretAndScopes; + + let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS); + expressions.addExpression("this"); + editor.focus(); + yield expressionsEvaluated; + + // Scroll to the top of the editor and inspect variables. + let breakpointScrollPosition = editor.getScrollInfo().top; + editor.setFirstVisibleLine(0); + let topmostScrollPosition = editor.getScrollInfo().top; + + ok(topmostScrollPosition < breakpointScrollPosition, + "The editor is now scrolled to the top (0)."); + is(editor.getFirstVisibleLine(), 0, + "The editor is now scrolled to the top (1)."); + + let failPopup = () => ok(false, "The popup has got unexpectedly hidden."); + let failScroll = () => ok(false, "The editor has got unexpectedly scrolled."); + tooltip.addEventListener("popuphiding", failPopup); + editorContainer.addEventListener("scroll", failScroll); + editor.on("scroll", () => { + if (editor.getScrollInfo().top > topmostScrollPosition) { + ok(false, "The editor scrolled back to the breakpoint location."); + } + }); + + expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS); + yield openVarPopup(panel, { line: 14, ch: 15 }); + yield expressionsEvaluated; + + tooltip.removeEventListener("popuphiding", failPopup); + editorContainer.removeEventListener("scroll", failScroll); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-11.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-11.js new file mode 100644 index 000000000..57d31c727 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-11.js @@ -0,0 +1,84 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the watch expression button is added in variable view popup. + */ + +const TAB_URL = EXAMPLE_URL + "doc_watch-expression-button.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let events = win.EVENTS; + let watch = win.DebuggerView.WatchExpressions; + let bubble = win.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + + let label = win.L10N.getStr("addWatchExpressionButton"); + let className = "dbg-expression-button"; + + function testExpressionButton(aLabel, aClassName, aExpression) { + ok(tooltip.querySelector("button"), + "There should be a button available in variable view popup."); + is(tooltip.querySelector("button").label, aLabel, + "The button available is labeled correctly."); + is(tooltip.querySelector("button").className, aClassName, + "The button available is styled correctly."); + + tooltip.querySelector("button").click(); + + ok(!tooltip.querySelector("button"), + "There should be no button available in variable view popup."); + ok(watch.getItemAtIndex(0), + "The expression at index 0 should be available."); + is(watch.getItemAtIndex(0).attachment.initialExpression, aExpression, + "The expression at index 0 is correct."); + } + + let onCaretAndScopes = waitForCaretAndScopes(panel, 19); + callInTab(tab, "start"); + yield onCaretAndScopes; + + // Inspect primitive value variable. + yield openVarPopup(panel, { line: 15, ch: 12 }); + let popupHiding = once(tooltip, "popuphiding"); + let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS); + testExpressionButton(label, className, "a"); + yield promise.all([popupHiding, expressionsEvaluated]); + ok(true, "The new watch expressions were re-evaluated and the panel got hidden (1)."); + + // Inspect non primitive value variable. + expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS); + yield openVarPopup(panel, { line: 16, ch: 12 }, true); + yield expressionsEvaluated; + ok(true, "The watch expressions were re-evaluated when a new panel opened (1)."); + + popupHiding = once(tooltip, "popuphiding"); + expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS); + testExpressionButton(label, className, "b"); + yield promise.all([popupHiding, expressionsEvaluated]); + ok(true, "The new watch expressions were re-evaluated and the panel got hidden (2)."); + + // Inspect property of an object. + expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS); + yield openVarPopup(panel, { line: 17, ch: 10 }); + yield expressionsEvaluated; + ok(true, "The watch expressions were re-evaluated when a new panel opened (2)."); + + popupHiding = once(tooltip, "popuphiding"); + expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS); + testExpressionButton(label, className, "b.a"); + yield promise.all([popupHiding, expressionsEvaluated]); + ok(true, "The new watch expressions were re-evaluated and the panel got hidden (3)."); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-12.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-12.js new file mode 100644 index 000000000..588276434 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-12.js @@ -0,0 +1,77 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the clicking "Watch" button twice, for the same expression, only adds it + * once. + */ + +const TAB_URL = EXAMPLE_URL + "doc_watch-expression-button.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let events = win.EVENTS; + let watch = win.DebuggerView.WatchExpressions; + let bubble = win.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + + function verifyContent(aExpression, aItemCount) { + + ok(watch.getItemAtIndex(0), + "The expression at index 0 should be available."); + is(watch.getItemAtIndex(0).attachment.initialExpression, aExpression, + "The expression at index 0 is correct."); + is(watch.itemCount, aItemCount, + "The expression count is correct."); + } + + let onCaretAndScopes = waitForCaretAndScopes(panel, 19); + callInTab(tab, "start"); + yield onCaretAndScopes; + + // Inspect primitive value variable. + yield openVarPopup(panel, { line: 15, ch: 12 }); + let popupHiding = once(tooltip, "popuphiding"); + let expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS); + tooltip.querySelector("button").click(); + verifyContent("a", 1); + yield promise.all([popupHiding, expressionsEvaluated]); + ok(true, "The new watch expressions were re-evaluated and the panel got hidden (1)."); + + // Inspect property of an object. + expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS); + yield openVarPopup(panel, { line: 17, ch: 10 }); + yield expressionsEvaluated; + ok(true, "The watch expressions were re-evaluated when a new panel opened (1)."); + + popupHiding = once(tooltip, "popuphiding"); + expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS); + tooltip.querySelector("button").click(); + verifyContent("b.a", 2); + yield promise.all([popupHiding, expressionsEvaluated]); + ok(true, "The new watch expressions were re-evaluated and the panel got hidden (2)."); + + // Re-inspect primitive value variable. + expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS); + yield openVarPopup(panel, { line: 15, ch: 12 }); + yield expressionsEvaluated; + ok(true, "The watch expressions were re-evaluated when a new panel opened (2)."); + + popupHiding = once(tooltip, "popuphiding"); + expressionsEvaluated = waitForDebuggerEvents(panel, events.FETCHED_WATCH_EXPRESSIONS); + tooltip.querySelector("button").click(); + verifyContent("b.a", 2); + yield promise.all([popupHiding, expressionsEvaluated]); + ok(true, "The new watch expressions were re-evaluated and the panel got hidden (3)."); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-13.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-13.js new file mode 100644 index 000000000..e8769ced7 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-13.js @@ -0,0 +1,68 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the variable inspection popup has inspector links for DOMNode + * properties and that the popup closes when the link is clicked + */ + +const TAB_URL = EXAMPLE_URL + "doc_domnode-variables.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let bubble = win.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + let toolbox = gDevTools.getToolbox(panel.target); + + function getDomNodeInTooltip(propertyName) { + let domNodeProperties = tooltip.querySelectorAll(".token-domnode"); + for (let prop of domNodeProperties) { + let propName = prop.parentNode.querySelector(".name"); + if (propName.getAttribute("value") === propertyName) { + ok(true, "DOMNode " + propertyName + " was found in the tooltip"); + return prop; + } + } + ok(false, "DOMNode " + propertyName + " wasn't found in the tooltip"); + } + + let onCaretAndScopes = waitForCaretAndScopes(panel, 19); + callInTab(tab, "start"); + yield onCaretAndScopes; + + // Inspect the div DOM variable. + yield openVarPopup(panel, { line: 17, ch: 38 }, true); + let property = getDomNodeInTooltip("firstElementChild"); + + // Simulate mouseover on the property value + let highlighted = once(toolbox, "node-highlight"); + EventUtils.sendMouseEvent({ type: "mouseover" }, property, + property.ownerDocument.defaultView); + yield highlighted; + ok(true, "The node-highlight event was fired on hover of the DOMNode"); + + // Simulate a click on the "select in inspector" button + let button = property.parentNode.querySelector(".variables-view-open-inspector"); + ok(button, "The select-in-inspector button is present"); + let inspectorSelected = once(toolbox, "inspector-selected"); + EventUtils.sendMouseEvent({ type: "mousedown" }, button, + button.ownerDocument.defaultView); + yield inspectorSelected; + ok(true, "The inspector got selected when clicked on the select-in-inspector"); + + // Make sure the inspector's initialization is finalized before ending the test + // Listening to the event *after* triggering the switch to the inspector isn't + // a problem as the inspector is asynchronously loaded. + yield once(toolbox.getPanel("inspector"), "inspector-updated"); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-14.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-14.js new file mode 100644 index 000000000..b1cefc8b8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-14.js @@ -0,0 +1,55 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests that the variable inspection popup is hidden when + * selecting text in the editor. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let bubble = win.DebuggerView.VariableBubble; + + let onCaretAndScopes = waitForCaretAndScopes(panel, 24); + callInTab(tab, "start"); + yield onCaretAndScopes; + + // Select some text. + let cursor = win.DebuggerView.editor.getOffset({ line: 15, ch: 12 }); + let [ anchor, head ] = win.DebuggerView.editor.getPosition( + cursor, + cursor + 3 + ); + win.DebuggerView.editor.setSelection(anchor, head); + + // Try to Inspect variable during selection. + let popupOpened = yield intendOpenVarPopup(panel, { line: 15, ch: 12 }, true); + + // Ensure the bubble is not there + ok(!popupOpened, + "The popup is not opened"); + ok(!bubble._markedText, + "The marked text in the editor is not there."); + + // Try to Inspect variable after selection. + popupOpened = yield intendOpenVarPopup(panel, { line: 15, ch: 12 }, false); + + // Ensure the bubble is not there + ok(popupOpened, + "The popup is opened"); + ok(bubble._markedText, + "The marked text in the editor is there."); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-15.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-15.js new file mode 100644 index 000000000..01c72df8c --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-15.js @@ -0,0 +1,39 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Tests opening the variable inspection popup directly on literals. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let bubble = win.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + + let onCaretAndScopes = waitForCaretAndScopes(panel, 24); + callInTab(tab, "start"); + yield onCaretAndScopes; + + yield openVarPopup(panel, { line: 15, ch: 12 }); + ok(true, "The variable inspection popup was shown for the real variable."); + + once(tooltip, "popupshown").then(() => { + ok(false, "The variable inspection popup shouldn't have been opened."); + }); + + reopenVarPopup(panel, { line: 17, ch: 27 }); + yield waitForTime(1000); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-16.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-16.js new file mode 100644 index 000000000..055517810 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-16.js @@ -0,0 +1,77 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +requestLongerTimeout(2); + +/** + * Tests if opening the variables inspection popup preserves the highlighting + * associated with the currently debugged line. + */ + +const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html"; + +function test() { + Task.spawn(function* () { + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let win = panel.panelWin; + let events = win.EVENTS; + let editor = win.DebuggerView.editor; + let frames = win.DebuggerView.StackFrames; + let variables = win.DebuggerView.Variables; + let bubble = win.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + + function checkView(selectedFrame, caretLine, debugLine = caretLine) { + let deferred = promise.defer(); + + is(win.gThreadClient.state, "paused", + "Should only be getting stack frames while paused."); + is(frames.itemCount, 25, + "Should have 25 frames."); + is(frames.selectedDepth, selectedFrame, + "The correct frame is selected in the widget."); + ok(isCaretPos(panel, caretLine), + "Editor caret location is correct."); + + // The editor's debug location takes a tick to update. + executeSoon(() => { + ok(isCaretPos(panel, caretLine), "Editor caret location is still correct."); + ok(isDebugPos(panel, debugLine), "Editor debug location is correct."); + deferred.resolve(); + }); + + return deferred.promise; + } + + function expandGlobalScope() { + let globalScope = variables.getScopeAtIndex(2); + is(globalScope.expanded, false, + "The globalScope should not be expanded yet."); + + let finished = waitForDebuggerEvents(panel, events.FETCHED_VARIABLES); + globalScope.expand(); + return finished; + } + + let onCaretAndScopes = waitForCaretAndScopes(panel, 26); + callInTab(tab, "recurse"); + yield onCaretAndScopes; + + yield checkView(0, 26); + + yield expandGlobalScope(); + yield checkView(0, 26); + + // Inspect variable in topmost frame. + yield openVarPopup(panel, { line: 26, ch: 11 }); + yield checkView(0, 26); + + yield resumeDebuggerThenCloseAndFinish(panel); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-17.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-17.js new file mode 100644 index 000000000..bdfe1a42b --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-popup-17.js @@ -0,0 +1,80 @@ +/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests opening the variable inspection popup while stopped at a debugger statement,
+ * clicking "step in" and verifying that the popup is gone.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_with-frame.html";
+
+let gTab, gPanel, gDebugger;
+let actions, gSources, gVariables;
+
+function test() {
+ let options = {
+ source: TAB_URL,
+ line: 1
+ };
+ initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
+ gTab = aTab;
+ gPanel = aPanel;
+ gDebugger = gPanel.panelWin;
+ actions = bindActionCreators(gPanel);
+ gSources = gDebugger.DebuggerView.Sources;
+ gVariables = gDebugger.DebuggerView.Variables;
+ let bubble = gDebugger.DebuggerView.VariableBubble;
+ let tooltip = bubble._tooltip.panel;
+ let testPopupHiding = Task.async(function* () {
+ yield addBreakpoint();
+ yield ensureThreadClientState(gPanel, "resumed");
+ yield pauseDebuggee();
+ yield openVarPopup(gPanel, { line: 20, ch: 17 });
+ is(tooltip.querySelectorAll(".devtools-tooltip-simple-text").length, 1,
+ "The popup should be open with a simple text entry");
+ // Now we're stopped at a breakpoint with an open popup
+ // we'll send a keypress and check if the popup closes
+ executeSoon(() => EventUtils.synthesizeKey("VK_F11", {}));
+ // The keypress should cause one resumed event and one paused event
+ yield waitForThreadEvents(gPanel, "resumed");
+ yield waitForThreadEvents(gPanel, "paused");
+ // Here's the state we're actually interested in checking..
+ checkVariablePopupClosed(bubble);
+ yield resumeDebuggerThenCloseAndFinish(gPanel);
+ });
+ testPopupHiding();
+ });
+}
+
+function addBreakpoint() {
+ return actions.addBreakpoint({ actor: gSources.selectedValue, line: 21 });
+}
+
+function pauseDebuggee() {
+ generateMouseClickInTab(gTab, "content.document.querySelector('button')");
+
+ // The first 'with' scope should be expanded by default, but the
+ // variables haven't been fetched yet. This is how 'with' scopes work.
+ return promise.all([
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES),
+ waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
+ ]);
+}
+
+function checkVariablePopupClosed(bubble) {
+ ok(!bubble.contentsShown(),
+ "When stepping, popup should close and be hidden.");
+ ok(bubble._tooltip.isEmpty(),
+ "The variable inspection popup should now be empty.");
+ ok(!bubble._markedText,
+ "The marked text in the editor was removed.");
+}
+
+registerCleanupFunction(function () {
+ gTab = null;
+ gPanel = null;
+ gDebugger = null;
+ actions = null;
+ gSources = null;
+ gVariables = null;
+});
diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-01.js new file mode 100644 index 000000000..4b68fb052 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-01.js @@ -0,0 +1,211 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view correctly re-expands nodes after pauses. + */ + +const TAB_URL = EXAMPLE_URL + "doc_with-frame.html"; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(4); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + const gVariables = gDebugger.DebuggerView.Variables; + const queries = gDebugger.require("./content/queries"); + const getState = gDebugger.DebuggerController.getState; + const actions = bindActionCreators(gPanel); + + // Always expand all items between pauses except 'window' variables. + gVariables.commitHierarchyIgnoredItems = Object.create(null, { window: { value: true } }); + + function addBreakpoint() { + return actions.addBreakpoint({ + actor: gSources.selectedValue, + line: 21 + }); + } + + function pauseDebuggee() { + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + + // The first 'with' scope should be expanded by default, but the + // variables haven't been fetched yet. This is how 'with' scopes work. + return promise.all([ + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES) + ]); + } + + function stepInDebuggee() { + // Spin the event loop before causing the debuggee to pause, to allow + // this function to return first. + executeSoon(() => { + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.querySelector("#step-in"), + gDebugger); + }); + + return promise.all([ + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES, 1), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 3), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1), + ]); + } + + function testVariablesExpand() { + let localScope = gVariables.getScopeAtIndex(0); + let withScope = gVariables.getScopeAtIndex(1); + let functionScope = gVariables.getScopeAtIndex(2); + let globalLexicalScope = gVariables.getScopeAtIndex(3); + let globalScope = gVariables.getScopeAtIndex(4); + + let thisVar = localScope.get("this"); + let windowVar = thisVar.get("window"); + + is(localScope.target.querySelector(".arrow").hasAttribute("open"), true, + "The localScope arrow should still be expanded."); + is(withScope.target.querySelector(".arrow").hasAttribute("open"), true, + "The withScope arrow should still be expanded."); + is(functionScope.target.querySelector(".arrow").hasAttribute("open"), true, + "The functionScope arrow should still be expanded."); + is(globalLexicalScope.target.querySelector(".arrow").hasAttribute("open"), true, + "The globalLexicalScope arrow should still be expanded."); + is(globalScope.target.querySelector(".arrow").hasAttribute("open"), true, + "The globalScope arrow should still be expanded."); + is(thisVar.target.querySelector(".arrow").hasAttribute("open"), true, + "The thisVar arrow should still be expanded."); + is(windowVar.target.querySelector(".arrow").hasAttribute("open"), false, + "The windowVar arrow should not be expanded."); + + is(localScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The localScope enumerables should still be expanded."); + is(withScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The withScope enumerables should still be expanded."); + is(functionScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The functionScope enumerables should still be expanded."); + is(globalLexicalScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The globalLexicalScope enumerables should still be expanded."); + is(globalScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The globalScope enumerables should still be expanded."); + is(thisVar.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The thisVar enumerables should still be expanded."); + is(windowVar.target.querySelector(".variables-view-element-details").hasAttribute("open"), false, + "The windowVar enumerables should not be expanded."); + + is(localScope.expanded, true, + "The localScope expanded getter should return true."); + is(withScope.expanded, true, + "The withScope expanded getter should return true."); + is(functionScope.expanded, true, + "The functionScope expanded getter should return true."); + is(globalLexicalScope.expanded, true, + "The globalScope expanded getter should return true."); + is(globalScope.expanded, true, + "The globalScope expanded getter should return true."); + is(thisVar.expanded, true, + "The thisVar expanded getter should return true."); + is(windowVar.expanded, false, + "The windowVar expanded getter should return true."); + } + + function prepareVariablesAndProperties() { + let deferred = promise.defer(); + + let localScope = gVariables.getScopeAtIndex(0); + let withScope = gVariables.getScopeAtIndex(1); + let functionScope = gVariables.getScopeAtIndex(2); + let globalLexicalScope = gVariables.getScopeAtIndex(3); + let globalScope = gVariables.getScopeAtIndex(4); + + is(localScope.expanded, true, + "The localScope should be expanded."); + is(withScope.expanded, false, + "The withScope should not be expanded yet."); + is(functionScope.expanded, false, + "The functionScope should not be expanded yet."); + is(globalLexicalScope.expanded, false, + "The globalLexicalScope should not be expanded yet."); + is(globalScope.expanded, false, + "The globalScope should not be expanded yet."); + + // Wait for only two events to be triggered, because the Function scope is + // an environment to which scope arguments and variables are already attached. + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 2).then(() => { + is(localScope.expanded, true, + "The localScope should now be expanded."); + is(withScope.expanded, true, + "The withScope should now be expanded."); + is(functionScope.expanded, true, + "The functionScope should now be expanded."); + is(globalLexicalScope.expanded, true, + "The globalLexicalScope should now be expanded."); + is(globalScope.expanded, true, + "The globalScope should now be expanded."); + + let thisVar = localScope.get("this"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + let windowVar = thisVar.get("window"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + let documentVar = windowVar.get("document"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + let locationVar = documentVar.get("location"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + is(thisVar.expanded, true, + "The local scope 'this' should be expanded."); + is(windowVar.expanded, true, + "The local scope 'this.window' should be expanded."); + is(documentVar.expanded, true, + "The local scope 'this.window.document' should be expanded."); + is(locationVar.expanded, true, + "The local scope 'this.window.document.location' should be expanded."); + + deferred.resolve(); + }); + + locationVar.expand(); + }); + + documentVar.expand(); + }); + + windowVar.expand(); + }); + + thisVar.expand(); + }); + + withScope.expand(); + functionScope.expand(); + globalLexicalScope.expand(); + globalScope.expand(); + + return deferred.promise; + } + + Task.spawn(function* () { + yield addBreakpoint(); + yield ensureThreadClientState(gPanel, "resumed"); + yield pauseDebuggee(); + yield prepareVariablesAndProperties(); + yield stepInDebuggee(); + yield testVariablesExpand(); + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-02.js new file mode 100644 index 000000000..e292c6804 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-02.js @@ -0,0 +1,226 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view correctly re-expands nodes after pauses, + * with the caveat that there are no ignored items in the hierarchy. + */ + +const TAB_URL = EXAMPLE_URL + "doc_with-frame.html"; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(4); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + const gVariables = gDebugger.DebuggerView.Variables; + const queries = gDebugger.require("./content/queries"); + const getState = gDebugger.DebuggerController.getState; + const actions = bindActionCreators(gPanel); + + // Always expand all items between pauses. + gVariables.commitHierarchyIgnoredItems = Object.create(null); + + function addBreakpoint() { + return actions.addBreakpoint({ + actor: gSources.selectedValue, + line: 21 + }); + } + + function pauseDebuggee() { + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + + // The first 'with' scope should be expanded by default, but the + // variables haven't been fetched yet. This is how 'with' scopes work. + return promise.all([ + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES) + ]); + } + + function stepInDebuggee() { + // Spin the event loop before causing the debuggee to pause, to allow + // this function to return first. + executeSoon(() => { + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.querySelector("#step-in"), + gDebugger); + }); + + return promise.all([ + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES, 1), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 3), + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 4), + ]); + } + + function testVariablesExpand() { + let localScope = gVariables.getScopeAtIndex(0); + let withScope = gVariables.getScopeAtIndex(1); + let functionScope = gVariables.getScopeAtIndex(2); + let globalLexicalScope = gVariables.getScopeAtIndex(3); + let globalScope = gVariables.getScopeAtIndex(4); + + let thisVar = localScope.get("this"); + let windowVar = thisVar.get("window"); + let documentVar = windowVar.get("document"); + let locationVar = documentVar.get("location"); + + is(localScope.target.querySelector(".arrow").hasAttribute("open"), true, + "The localScope arrow should still be expanded."); + is(withScope.target.querySelector(".arrow").hasAttribute("open"), true, + "The withScope arrow should still be expanded."); + is(functionScope.target.querySelector(".arrow").hasAttribute("open"), true, + "The functionScope arrow should still be expanded."); + is(globalLexicalScope.target.querySelector(".arrow").hasAttribute("open"), true, + "The globalLexicalScope arrow should still be expanded."); + is(globalScope.target.querySelector(".arrow").hasAttribute("open"), true, + "The globalScope arrow should still be expanded."); + is(thisVar.target.querySelector(".arrow").hasAttribute("open"), true, + "The thisVar arrow should still be expanded."); + is(windowVar.target.querySelector(".arrow").hasAttribute("open"), true, + "The windowVar arrow should still be expanded."); + is(documentVar.target.querySelector(".arrow").hasAttribute("open"), true, + "The documentVar arrow should still be expanded."); + is(locationVar.target.querySelector(".arrow").hasAttribute("open"), true, + "The locationVar arrow should still be expanded."); + + is(localScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The localScope enumerables should still be expanded."); + is(withScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The withScope enumerables should still be expanded."); + is(functionScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The functionScope enumerables should still be expanded."); + is(globalLexicalScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The globalLexicalScope enumerables should still be expanded."); + is(globalScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The globalScope enumerables should still be expanded."); + is(thisVar.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The thisVar enumerables should still be expanded."); + is(windowVar.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The windowVar enumerables should still be expanded."); + is(documentVar.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The documentVar enumerables should still be expanded."); + is(locationVar.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The locationVar enumerables should still be expanded."); + + is(localScope.expanded, true, + "The localScope expanded getter should return true."); + is(withScope.expanded, true, + "The withScope expanded getter should return true."); + is(functionScope.expanded, true, + "The functionScope expanded getter should return true."); + is(globalLexicalScope.expanded, true, + "The globalLexicalScope expanded getter should return true."); + is(globalScope.expanded, true, + "The globalScope expanded getter should return true."); + is(thisVar.expanded, true, + "The thisVar expanded getter should return true."); + is(windowVar.expanded, true, + "The windowVar expanded getter should return true."); + is(documentVar.expanded, true, + "The documentVar expanded getter should return true."); + is(locationVar.expanded, true, + "The locationVar expanded getter should return true."); + } + + function prepareVariablesAndProperties() { + let deferred = promise.defer(); + + let localScope = gVariables.getScopeAtIndex(0); + let withScope = gVariables.getScopeAtIndex(1); + let functionScope = gVariables.getScopeAtIndex(2); + let globalLexicalScope = gVariables.getScopeAtIndex(3); + let globalScope = gVariables.getScopeAtIndex(4); + + is(localScope.expanded, true, + "The localScope should be expanded."); + is(withScope.expanded, false, + "The withScope should not be expanded yet."); + is(functionScope.expanded, false, + "The functionScope should not be expanded yet."); + is(globalLexicalScope.expanded, false, + "The globalLexicalScope should not be expanded yet."); + is(globalScope.expanded, false, + "The globalScope should not be expanded yet."); + + // Wait for only two events to be triggered, because the Function scope is + // an environment to which scope arguments and variables are already attached. + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES, 2).then(() => { + is(localScope.expanded, true, + "The localScope should now be expanded."); + is(withScope.expanded, true, + "The withScope should now be expanded."); + is(functionScope.expanded, true, + "The functionScope should now be expanded."); + is(globalLexicalScope.expanded, true, + "The globalLexicalScope should now be expanded."); + is(globalScope.expanded, true, + "The globalScope should now be expanded."); + + let thisVar = localScope.get("this"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + let windowVar = thisVar.get("window"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + let documentVar = windowVar.get("document"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + let locationVar = documentVar.get("location"); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 1).then(() => { + is(thisVar.expanded, true, + "The local scope 'this' should be expanded."); + is(windowVar.expanded, true, + "The local scope 'this.window' should be expanded."); + is(documentVar.expanded, true, + "The local scope 'this.window.document' should be expanded."); + is(locationVar.expanded, true, + "The local scope 'this.window.document.location' should be expanded."); + + deferred.resolve(); + }); + + locationVar.expand(); + }); + + documentVar.expand(); + }); + + windowVar.expand(); + }); + + thisVar.expand(); + }); + + withScope.expand(); + functionScope.expand(); + globalLexicalScope.expand(); + globalScope.expand(); + + return deferred.promise; + } + + Task.spawn(function* () { + yield addBreakpoint(); + yield ensureThreadClientState(gPanel, "resumed"); + yield pauseDebuggee(); + yield prepareVariablesAndProperties(); + yield stepInDebuggee(); + yield testVariablesExpand(); + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-03.js new file mode 100644 index 000000000..258fbed26 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-reexpand-03.js @@ -0,0 +1,120 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view correctly re-expands *scopes* after pauses. + */ + +const TAB_URL = EXAMPLE_URL + "doc_scope-variable-4.html"; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(4); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + const gTab = aTab; + const gPanel = aPanel; + const gDebugger = gPanel.panelWin; + const gSources = gDebugger.DebuggerView.Sources; + const gVariables = gDebugger.DebuggerView.Variables; + const queries = gDebugger.require("./content/queries"); + const getState = gDebugger.DebuggerController.getState; + const actions = bindActionCreators(gPanel); + + // Always expand all items between pauses. + gVariables.commitHierarchyIgnoredItems = Object.create(null); + + function addBreakpoint() { + return actions.addBreakpoint({ + actor: gSources.selectedValue, + line: 18 + }); + } + + function pauseDebuggee() { + callInTab(gTab, "test"); + + return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES); + } + + function resumeDebuggee() { + // Spin the event loop before causing the debuggee to pause, to allow + // this function to return first. + executeSoon(() => { + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.querySelector("#resume"), + gDebugger); + }); + + return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES); + } + + function testVariablesExpand() { + let localScope = gVariables.getScopeAtIndex(0); + let functionScope = gVariables.getScopeAtIndex(1); + let globalScope = gVariables.getScopeAtIndex(2); + + is(localScope.target.querySelector(".arrow").hasAttribute("open"), true, + "The localScope arrow should still be expanded."); + is(functionScope.target.querySelector(".arrow").hasAttribute("open"), true, + "The functionScope arrow should still be expanded."); + is(globalScope.target.querySelector(".arrow").hasAttribute("open"), false, + "The globalScope arrow should not be expanded."); + + is(localScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The localScope enumerables should still be expanded."); + is(functionScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), true, + "The functionScope enumerables should still be expanded."); + is(globalScope.target.querySelector(".variables-view-element-details").hasAttribute("open"), false, + "The globalScope enumerables should not be expanded."); + + is(localScope.expanded, true, + "The localScope expanded getter should return true."); + is(functionScope.expanded, true, + "The functionScope expanded getter should return true."); + is(globalScope.expanded, false, + "The globalScope expanded getter should return false."); + } + + function prepareScopes() { + let localScope = gVariables.getScopeAtIndex(0); + let functionScope = gVariables.getScopeAtIndex(1); + let globalScope = gVariables.getScopeAtIndex(2); + + is(localScope.expanded, true, + "The localScope should be expanded."); + is(functionScope.expanded, false, + "The functionScope should not be expanded yet."); + is(globalScope.expanded, false, + "The globalScope should not be expanded yet."); + + localScope.collapse(); + functionScope.expand(); + + // Don't for any events to be triggered, because the Function scope is + // an environment to which scope arguments and variables are already attached. + is(localScope.expanded, false, + "The localScope should not be expanded anymore."); + is(functionScope.expanded, true, + "The functionScope should now be expanded."); + is(globalScope.expanded, false, + "The globalScope should still not be expanded."); + } + + Task.spawn(function* () { + yield addBreakpoint(); + yield ensureThreadClientState(gPanel, "resumed"); + yield pauseDebuggee(); + yield prepareScopes(); + yield resumeDebuggee(); + yield testVariablesExpand(); + resumeDebuggerThenCloseAndFinish(gPanel); + }); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-webidl.js b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-webidl.js new file mode 100644 index 000000000..4499ec18f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-webidl.js @@ -0,0 +1,262 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Make sure that the variables view correctly displays WebIDL attributes in DOM + * objects. + */ + +const TAB_URL = EXAMPLE_URL + "doc_frame-parameters.html"; + +var gTab, gPanel, gDebugger; +var gVariables; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(2); + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gVariables = gDebugger.DebuggerView.Variables; + + waitForCaretAndScopes(gPanel, 24) + .then(expandGlobalScope) + .then(performTest) + .then(() => resumeDebuggerThenCloseAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + + generateMouseClickInTab(gTab, "content.document.querySelector('button')"); + }); +} + +function expandGlobalScope() { + let deferred = promise.defer(); + + let globalScope = gVariables.getScopeAtIndex(2); + is(globalScope.expanded, false, + "The global scope should not be expanded by default."); + + gDebugger.once(gDebugger.EVENTS.FETCHED_VARIABLES, deferred.resolve); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + globalScope.target.querySelector(".name"), + gDebugger); + + return deferred.promise; +} + +function performTest() { + let deferred = promise.defer(); + let globalScope = gVariables.getScopeAtIndex(2); + + let buttonVar = globalScope.get("button"); + let buttonAsProtoVar = globalScope.get("buttonAsProto"); + let documentVar = globalScope.get("document"); + + is(buttonVar.target.querySelector(".name").getAttribute("value"), "button", + "Should have the right property name for 'button'."); + is(buttonVar.target.querySelector(".value").getAttribute("value"), "<button>", + "Should have the right property value for 'button'."); + ok(buttonVar.target.querySelector(".value").className.includes("token-domnode"), + "Should have the right token class for 'button'."); + + is(buttonAsProtoVar.target.querySelector(".name").getAttribute("value"), "buttonAsProto", + "Should have the right property name for 'buttonAsProto'."); + is(buttonAsProtoVar.target.querySelector(".value").getAttribute("value"), "Object", + "Should have the right property value for 'buttonAsProto'."); + ok(buttonAsProtoVar.target.querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'buttonAsProto'."); + + is(documentVar.target.querySelector(".name").getAttribute("value"), "document", + "Should have the right property name for 'document'."); + is(documentVar.target.querySelector(".value").getAttribute("value"), + "HTMLDocument \u2192 doc_frame-parameters.html", + "Should have the right property value for 'document'."); + ok(documentVar.target.querySelector(".value").className.includes("token-domnode"), + "Should have the right token class for 'document'."); + + is(buttonVar.expanded, false, + "The buttonVar should not be expanded at this point."); + is(buttonAsProtoVar.expanded, false, + "The buttonAsProtoVar should not be expanded at this point."); + is(documentVar.expanded, false, + "The documentVar should not be expanded at this point."); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 3).then(() => { + is(buttonVar.get("type").target.querySelector(".name").getAttribute("value"), "type", + "Should have the right property name for 'type'."); + is(buttonVar.get("type").target.querySelector(".value").getAttribute("value"), "\"submit\"", + "Should have the right property value for 'type'."); + ok(buttonVar.get("type").target.querySelector(".value").className.includes("token-string"), + "Should have the right token class for 'type'."); + + is(buttonVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes", + "Should have the right property name for 'childNodes'."); + is(buttonVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList[1]", + "Should have the right property value for 'childNodes'."); + ok(buttonVar.get("childNodes").target.querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'childNodes'."); + + is(buttonVar.get("onclick").target.querySelector(".name").getAttribute("value"), "onclick", + "Should have the right property name for 'onclick'."); + is(buttonVar.get("onclick").target.querySelector(".value").getAttribute("value"), "onclick(event)", + "Should have the right property value for 'onclick'."); + ok(buttonVar.get("onclick").target.querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'onclick'."); + + is(documentVar.get("title").target.querySelector(".name").getAttribute("value"), "title", + "Should have the right property name for 'title'."); + is(documentVar.get("title").target.querySelector(".value").getAttribute("value"), "\"Debugger test page\"", + "Should have the right property value for 'title'."); + ok(documentVar.get("title").target.querySelector(".value").className.includes("token-string"), + "Should have the right token class for 'title'."); + + is(documentVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes", + "Should have the right property name for 'childNodes'."); + is(documentVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList[3]", + "Should have the right property value for 'childNodes'."); + ok(documentVar.get("childNodes").target.querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'childNodes'."); + + is(documentVar.get("onclick").target.querySelector(".name").getAttribute("value"), "onclick", + "Should have the right property name for 'onclick'."); + is(documentVar.get("onclick").target.querySelector(".value").getAttribute("value"), "null", + "Should have the right property value for 'onclick'."); + ok(documentVar.get("onclick").target.querySelector(".value").className.includes("token-null"), + "Should have the right token class for 'onclick'."); + + let buttonProtoVar = buttonVar.get("__proto__"); + let buttonAsProtoProtoVar = buttonAsProtoVar.get("__proto__"); + let documentProtoVar = documentVar.get("__proto__"); + + is(buttonProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__", + "Should have the right property name for '__proto__'."); + is(buttonProtoVar.target.querySelector(".value").getAttribute("value"), "HTMLButtonElementPrototype", + "Should have the right property value for '__proto__'."); + ok(buttonProtoVar.target.querySelector(".value").className.includes("token-other"), + "Should have the right token class for '__proto__'."); + + is(buttonAsProtoProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__", + "Should have the right property name for '__proto__'."); + is(buttonAsProtoProtoVar.target.querySelector(".value").getAttribute("value"), "<button>", + "Should have the right property value for '__proto__'."); + ok(buttonAsProtoProtoVar.target.querySelector(".value").className.includes("token-domnode"), + "Should have the right token class for '__proto__'."); + + is(documentProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__", + "Should have the right property name for '__proto__'."); + is(documentProtoVar.target.querySelector(".value").getAttribute("value"), "HTMLDocumentPrototype", + "Should have the right property value for '__proto__'."); + ok(documentProtoVar.target.querySelector(".value").className.includes("token-other"), + "Should have the right token class for '__proto__'."); + + is(buttonProtoVar.expanded, false, + "The buttonProtoVar should not be expanded at this point."); + is(buttonAsProtoProtoVar.expanded, false, + "The buttonAsProtoProtoVar should not be expanded at this point."); + is(documentProtoVar.expanded, false, + "The documentProtoVar should not be expanded at this point."); + + waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_PROPERTIES, 3).then(() => { + is(buttonAsProtoProtoVar.get("type").target.querySelector(".name").getAttribute("value"), "type", + "Should have the right property name for 'type'."); + is(buttonAsProtoProtoVar.get("type").target.querySelector(".value").getAttribute("value"), "\"submit\"", + "Should have the right property value for 'type'."); + ok(buttonAsProtoProtoVar.get("type").target.querySelector(".value").className.includes("token-string"), + "Should have the right token class for 'type'."); + + is(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".name").getAttribute("value"), "childNodes", + "Should have the right property name for 'childNodes'."); + is(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".value").getAttribute("value"), "NodeList[1]", + "Should have the right property value for 'childNodes'."); + ok(buttonAsProtoProtoVar.get("childNodes").target.querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'childNodes'."); + + is(buttonAsProtoProtoVar.get("onclick").target.querySelector(".name").getAttribute("value"), "onclick", + "Should have the right property name for 'onclick'."); + is(buttonAsProtoProtoVar.get("onclick").target.querySelector(".value").getAttribute("value"), "onclick(event)", + "Should have the right property value for 'onclick'."); + ok(buttonAsProtoProtoVar.get("onclick").target.querySelector(".value").className.includes("token-other"), + "Should have the right token class for 'onclick'."); + + let buttonProtoProtoVar = buttonProtoVar.get("__proto__"); + let buttonAsProtoProtoProtoVar = buttonAsProtoProtoVar.get("__proto__"); + let documentProtoProtoVar = documentProtoVar.get("__proto__"); + + is(buttonProtoProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__", + "Should have the right property name for '__proto__'."); + is(buttonProtoProtoVar.target.querySelector(".value").getAttribute("value"), "HTMLElementPrototype", + "Should have the right property value for '__proto__'."); + ok(buttonProtoProtoVar.target.querySelector(".value").className.includes("token-other"), + "Should have the right token class for '__proto__'."); + + is(buttonAsProtoProtoProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__", + "Should have the right property name for '__proto__'."); + is(buttonAsProtoProtoProtoVar.target.querySelector(".value").getAttribute("value"), "HTMLButtonElementPrototype", + "Should have the right property value for '__proto__'."); + ok(buttonAsProtoProtoProtoVar.target.querySelector(".value").className.includes("token-other"), + "Should have the right token class for '__proto__'."); + + is(documentProtoProtoVar.target.querySelector(".name").getAttribute("value"), "__proto__", + "Should have the right property name for '__proto__'."); + is(documentProtoProtoVar.target.querySelector(".value").getAttribute("value"), "DocumentPrototype", + "Should have the right property value for '__proto__'."); + ok(documentProtoProtoVar.target.querySelector(".value").className.includes("token-other"), + "Should have the right token class for '__proto__'."); + + is(buttonAsProtoProtoProtoVar.expanded, false, + "The buttonAsProtoProtoProtoVar should not be expanded at this point."); + is(buttonAsProtoProtoProtoVar.expanded, false, + "The buttonAsProtoProtoProtoVar should not be expanded at this point."); + is(documentProtoProtoVar.expanded, false, + "The documentProtoProtoVar should not be expanded at this point."); + + deferred.resolve(); + }); + + // Similarly, expand the 'button.__proto__', 'buttonAsProto.__proto__' and + // 'document.__proto__' variables view nodes. + buttonProtoVar.expand(); + buttonAsProtoProtoVar.expand(); + documentProtoVar.expand(); + + is(buttonProtoVar.expanded, true, + "The buttonProtoVar should be immediately marked as expanded."); + is(buttonAsProtoProtoVar.expanded, true, + "The buttonAsProtoProtoVar should be immediately marked as expanded."); + is(documentProtoVar.expanded, true, + "The documentProtoVar should be immediately marked as expanded."); + }); + + // Expand the 'button', 'buttonAsProto' and 'document' variables view nodes. + // This causes their properties to be retrieved and displayed. + buttonVar.expand(); + buttonAsProtoVar.expand(); + documentVar.expand(); + + is(buttonVar.expanded, true, + "The buttonVar should be immediately marked as expanded."); + is(buttonAsProtoVar.expanded, true, + "The buttonAsProtoVar should be immediately marked as expanded."); + is(documentVar.expanded, true, + "The documentVar should be immediately marked as expanded."); + + return deferred.promise; +} + +registerCleanupFunction(function () { + gTab = null; + gPanel = null; + gDebugger = null; + gVariables = null; +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-01.js new file mode 100644 index 000000000..fe55a5561 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-01.js @@ -0,0 +1,227 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 727429: Test the debugger watch expressions. + */ + +const TAB_URL = EXAMPLE_URL + "doc_watch-expressions.html"; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(2); + + let gTab, gPanel, gDebugger; + let gEditor, gWatch, gVariables; + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gEditor = gDebugger.DebuggerView.editor; + gWatch = gDebugger.DebuggerView.WatchExpressions; + gVariables = gDebugger.DebuggerView.Variables; + + gDebugger.DebuggerView.toggleInstrumentsPane({ visible: true, animated: false }); + + performTest(); + closeDebuggerAndFinish(gPanel); + }); + + function performTest() { + is(gWatch.getAllStrings().length, 0, + "There should initially be no watch expressions."); + + addAndCheckExpressions(1, 0, "a"); + addAndCheckExpressions(2, 0, "b"); + addAndCheckExpressions(3, 0, "c"); + + removeAndCheckExpression(2, 1, "a"); + removeAndCheckExpression(1, 0, "a"); + + addAndCheckExpressions(2, 0, "", true); + gEditor.focus(); + is(gWatch.getAllStrings().length, 1, + "Empty watch expressions are automatically removed."); + + addAndCheckExpressions(2, 0, "a", true); + gEditor.focus(); + is(gWatch.getAllStrings().length, 1, + "Duplicate watch expressions are automatically removed."); + + addAndCheckExpressions(2, 0, "a\t", true); + addAndCheckExpressions(2, 0, "a\r", true); + addAndCheckExpressions(2, 0, "a\n", true); + gEditor.focus(); + is(gWatch.getAllStrings().length, 1, + "Duplicate watch expressions are automatically removed."); + + addAndCheckExpressions(2, 0, "\ta", true); + addAndCheckExpressions(2, 0, "\ra", true); + addAndCheckExpressions(2, 0, "\na", true); + gEditor.focus(); + is(gWatch.getAllStrings().length, 1, + "Duplicate watch expressions are automatically removed."); + + addAndCheckCustomExpression(2, 0, "bazΩΩka"); + addAndCheckCustomExpression(3, 0, "bambøøcha"); + + EventUtils.sendMouseEvent({ type: "click" }, + gWatch.getItemAtIndex(0).attachment.view.closeNode, + gDebugger); + + is(gWatch.getAllStrings().length, 2, + "Watch expressions are removed when the close button is pressed."); + is(gWatch.getAllStrings()[0], "bazΩΩka", + "The expression at index " + 0 + " should be correct (1)."); + is(gWatch.getAllStrings()[1], "a", + "The expression at index " + 1 + " should be correct (2)."); + + EventUtils.sendMouseEvent({ type: "click" }, + gWatch.getItemAtIndex(0).attachment.view.closeNode, + gDebugger); + + is(gWatch.getAllStrings().length, 1, + "Watch expressions are removed when the close button is pressed."); + is(gWatch.getAllStrings()[0], "a", + "The expression at index " + 0 + " should be correct (3)."); + + EventUtils.sendMouseEvent({ type: "click" }, + gWatch.getItemAtIndex(0).attachment.view.closeNode, + gDebugger); + + is(gWatch.getAllStrings().length, 0, + "Watch expressions are removed when the close button is pressed."); + + EventUtils.sendMouseEvent({ type: "click" }, + gWatch.widget._parent, + gDebugger); + + is(gWatch.getAllStrings().length, 1, + "Watch expressions are added when the view container is pressed."); + } + + function addAndCheckCustomExpression(aTotal, aIndex, aString, noBlur) { + addAndCheckExpressions(aTotal, aIndex, "", true); + + EventUtils.sendString(aString, gDebugger); + + gEditor.focus(); + + let element = gWatch.getItemAtIndex(aIndex).target; + + is(gWatch.getItemAtIndex(aIndex).attachment.initialExpression, "", + "The initial expression at index " + aIndex + " should be correct (1)."); + is(gWatch.getItemForElement(element).attachment.initialExpression, "", + "The initial expression at index " + aIndex + " should be correct (2)."); + + is(gWatch.getItemAtIndex(aIndex).attachment.currentExpression, aString, + "The expression at index " + aIndex + " should be correct (1)."); + is(gWatch.getItemForElement(element).attachment.currentExpression, aString, + "The expression at index " + aIndex + " should be correct (2)."); + + is(gWatch.getString(aIndex), aString, + "The expression at index " + aIndex + " should be correct (3)."); + is(gWatch.getAllStrings()[aIndex], aString, + "The expression at index " + aIndex + " should be correct (4)."); + } + + function addAndCheckExpressions(aTotal, aIndex, aString, noBlur) { + gWatch.addExpression(aString); + + is(gWatch.getAllStrings().length, aTotal, + "There should be " + aTotal + " watch expressions available (1)."); + is(gWatch.itemCount, aTotal, + "There should be " + aTotal + " watch expressions available (2)."); + + ok(gWatch.getItemAtIndex(aIndex), + "The expression at index " + aIndex + " should be available."); + is(gWatch.getItemAtIndex(aIndex).attachment.initialExpression, aString, + "The expression at index " + aIndex + " should have an initial expression."); + + let element = gWatch.getItemAtIndex(aIndex).target; + + ok(element, + "There should be a new expression item in the view."); + ok(gWatch.getItemForElement(element), + "The watch expression item should be accessible."); + is(gWatch.getItemForElement(element), gWatch.getItemAtIndex(aIndex), + "The correct watch expression item was accessed."); + + ok(gWatch.widget.getItemAtIndex(aIndex) instanceof XULElement, + "The correct watch expression element was accessed (1)."); + is(element, gWatch.widget.getItemAtIndex(aIndex), + "The correct watch expression element was accessed (2)."); + + is(gWatch.getItemForElement(element).attachment.view.arrowNode.hidden, false, + "The arrow node should be visible."); + is(gWatch.getItemForElement(element).attachment.view.closeNode.hidden, false, + "The close button should be visible."); + is(gWatch.getItemForElement(element).attachment.view.inputNode.getAttribute("focused"), "true", + "The textbox input should be focused."); + + is(gVariables.parentNode.scrollTop, 0, + "The variables view should be scrolled to top"); + + is(gWatch.items[0], gWatch.getItemAtIndex(aIndex), + "The correct watch expression was added to the cache (1)."); + is(gWatch.items[0], gWatch.getItemForElement(element), + "The correct watch expression was added to the cache (2)."); + + if (!noBlur) { + gEditor.focus(); + + is(gWatch.getItemAtIndex(aIndex).attachment.initialExpression, aString, + "The initial expression at index " + aIndex + " should be correct (1)."); + is(gWatch.getItemForElement(element).attachment.initialExpression, aString, + "The initial expression at index " + aIndex + " should be correct (2)."); + + is(gWatch.getItemAtIndex(aIndex).attachment.currentExpression, aString, + "The expression at index " + aIndex + " should be correct (1)."); + is(gWatch.getItemForElement(element).attachment.currentExpression, aString, + "The expression at index " + aIndex + " should be correct (2)."); + + is(gWatch.getString(aIndex), aString, + "The expression at index " + aIndex + " should be correct (3)."); + is(gWatch.getAllStrings()[aIndex], aString, + "The expression at index " + aIndex + " should be correct (4)."); + } + } + + function removeAndCheckExpression(aTotal, aIndex, aString) { + gWatch.removeAt(aIndex); + + is(gWatch.getAllStrings().length, aTotal, + "There should be " + aTotal + " watch expressions available (1)."); + is(gWatch.itemCount, aTotal, + "There should be " + aTotal + " watch expressions available (2)."); + + ok(gWatch.getItemAtIndex(aIndex), + "The expression at index " + aIndex + " should still be available."); + is(gWatch.getItemAtIndex(aIndex).attachment.initialExpression, aString, + "The expression at index " + aIndex + " should still have an initial expression."); + + let element = gWatch.getItemAtIndex(aIndex).target; + + is(gWatch.getItemAtIndex(aIndex).attachment.initialExpression, aString, + "The initial expression at index " + aIndex + " should be correct (1)."); + is(gWatch.getItemForElement(element).attachment.initialExpression, aString, + "The initial expression at index " + aIndex + " should be correct (2)."); + + is(gWatch.getItemAtIndex(aIndex).attachment.currentExpression, aString, + "The expression at index " + aIndex + " should be correct (1)."); + is(gWatch.getItemForElement(element).attachment.currentExpression, aString, + "The expression at index " + aIndex + " should be correct (2)."); + + is(gWatch.getString(aIndex), aString, + "The expression at index " + aIndex + " should be correct (3)."); + is(gWatch.getAllStrings()[aIndex], aString, + "The expression at index " + aIndex + " should be correct (4)."); + } +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-02.js new file mode 100644 index 000000000..a9b22708d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-02.js @@ -0,0 +1,383 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * Bug 727429: Test the debugger watch expressions. + */ + +const TAB_URL = EXAMPLE_URL + "doc_watch-expressions.html"; + +function test() { + // Debug test slaves are a bit slow at this test. + requestLongerTimeout(2); + + let gTab, gPanel, gDebugger; + let gWatch, gVariables; + + let options = { + source: TAB_URL, + line: 1 + }; + initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => { + gTab = aTab; + gPanel = aPanel; + gDebugger = gPanel.panelWin; + gWatch = gDebugger.DebuggerView.WatchExpressions; + gVariables = gDebugger.DebuggerView.Variables; + + gDebugger.DebuggerView.toggleInstrumentsPane({ visible: true, animated: false }); + + addExpressions(); + performTest() + .then(finishTest) + .then(() => closeDebuggerAndFinish(gPanel)) + .then(null, aError => { + ok(false, "Got an error: " + aError.message + "\n" + aError.stack); + }); + }); + + function addExpressions() { + // https://bugzilla.mozilla.org/show_bug.cgi?id=1205353 + // BZ#1205353 - wrong result for string replace with backslash-dollar. + gWatch.addExpression("'$$$'.replace(/\\$/, 'foo')"); + gWatch.addExpression("'a'"); + gWatch.addExpression("\"a\""); + gWatch.addExpression("'a\"\"'"); + gWatch.addExpression("\"a''\""); + gWatch.addExpression("?"); + gWatch.addExpression("a"); + gWatch.addExpression("this"); + gWatch.addExpression("this.canada"); + gWatch.addExpression("[1, 2, 3]"); + gWatch.addExpression("x = [1, 2, 3]"); + gWatch.addExpression("y = [1, 2, 3]; y.test = 4"); + gWatch.addExpression("z = [1, 2, 3]; z.test = 4; z"); + gWatch.addExpression("t = [1, 2, 3]; t.test = 4; !t"); + gWatch.addExpression("arguments[0]"); + gWatch.addExpression("encodeURI(\"\\\")"); + gWatch.addExpression("decodeURI(\"\\\")"); + gWatch.addExpression("decodeURIComponent(\"%\")"); + gWatch.addExpression("//"); + gWatch.addExpression("// 42"); + gWatch.addExpression("{}.foo"); + gWatch.addExpression("{}.foo()"); + gWatch.addExpression("({}).foo()"); + gWatch.addExpression("new Array(-1)"); + gWatch.addExpression("4.2.toExponential(-4.2)"); + gWatch.addExpression("throw new Error(\"bazinga\")"); + gWatch.addExpression("({ get error() { throw new Error(\"bazinga\") } }).error"); + gWatch.addExpression("throw { get name() { throw \"bazinga\" } }"); + + } + + function performTest() { + let deferred = promise.defer(); + + is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 0, + "There should be 0 hidden nodes in the watch expressions container"); + is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 28, + "There should be 28 visible nodes in the watch expressions container"); + + test1(function () { + test2(function () { + test3(function () { + test4(function () { + test5(function () { + test6(function () { + test7(function () { + test8(function () { + test9(function () { + deferred.resolve(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + + return deferred.promise; + } + + function finishTest() { + is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, 0, + "There should be 0 hidden nodes in the watch expressions container"); + is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 28, + "There should be 28 visible nodes in the watch expressions container"); + } + + function test1(aCallback) { + gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => { + checkWatchExpressions(27, { + a: "ReferenceError: a is not defined", + this: { type: "object", class: "Object" }, + prop: { type: "object", class: "String" }, + args: { type: "undefined" } + }); + aCallback(); + }); + + callInTab(gTab, "test"); + } + + function test2(aCallback) { + gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => { + checkWatchExpressions(27, { + a: { type: "undefined" }, + this: { type: "object", class: "Window" }, + prop: { type: "undefined" }, + args: "sensational" + }); + aCallback(); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + } + + function test3(aCallback) { + gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => { + checkWatchExpressions(27, { + a: { type: "object", class: "Object" }, + this: { type: "object", class: "Window" }, + prop: { type: "undefined" }, + args: "sensational" + }); + aCallback(); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + } + + function test4(aCallback) { + gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => { + checkWatchExpressions(28, { + a: 5, + this: { type: "object", class: "Window" }, + prop: { type: "undefined" }, + args: "sensational" + }); + aCallback(); + }); + + gWatch.addExpression("a = 5"); + EventUtils.sendKey("RETURN", gDebugger); + } + + function test5(aCallback) { + gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => { + checkWatchExpressions(28, { + a: 5, + this: { type: "object", class: "Window" }, + prop: { type: "undefined" }, + args: "sensational" + }); + aCallback(); + }); + + gWatch.addExpression("encodeURI(\"\\\")"); + EventUtils.sendKey("RETURN", gDebugger); + } + + function test6(aCallback) { + gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => { + checkWatchExpressions(28, { + a: 5, + this: { type: "object", class: "Window" }, + prop: { type: "undefined" }, + args: "sensational" + }); + aCallback(); + }); + + gWatch.addExpression("decodeURI(\"\\\")"); + EventUtils.sendKey("RETURN", gDebugger); + } + + function test7(aCallback) { + gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => { + checkWatchExpressions(28, { + a: 5, + this: { type: "object", class: "Window" }, + prop: { type: "undefined" }, + args: "sensational" + }); + aCallback(); + }); + + gWatch.addExpression("?"); + EventUtils.sendKey("RETURN", gDebugger); + } + + function test8(aCallback) { + gDebugger.once(gDebugger.EVENTS.FETCHED_WATCH_EXPRESSIONS, () => { + checkWatchExpressions(28, { + a: 5, + this: { type: "object", class: "Window" }, + prop: { type: "undefined" }, + args: "sensational" + }); + aCallback(); + }); + + gWatch.addExpression("a"); + EventUtils.sendKey("RETURN", gDebugger); + } + + function test9(aCallback) { + gDebugger.once(gDebugger.EVENTS.AFTER_FRAMES_CLEARED, () => { + aCallback(); + }); + + EventUtils.sendMouseEvent({ type: "mousedown" }, + gDebugger.document.getElementById("resume"), + gDebugger); + } + + function checkWatchExpressions(aTotal, aExpectedExpressions) { + let { + a: expected_a, + this: expected_this, + prop: expected_prop, + args: expected_args + } = aExpectedExpressions; + + is(gDebugger.document.querySelectorAll(".dbg-expression[hidden=true]").length, aTotal, + "There should be " + aTotal + " hidden nodes in the watch expressions container."); + is(gDebugger.document.querySelectorAll(".dbg-expression:not([hidden=true])").length, 0, + "There should be 0 visible nodes in the watch expressions container."); + + let label = gDebugger.L10N.getStr("watchExpressionsScopeLabel"); + let scope = gVariables._currHierarchy.get(label); + + ok(scope, "There should be a wach expressions scope in the variables view."); + is(scope._store.size, aTotal, "There should be " + aTotal + " evaluations availalble."); + + let w1 = scope.get("'a'"); + let w2 = scope.get("\"a\""); + let w3 = scope.get("'a\"\"'"); + let w4 = scope.get("\"a''\""); + let w5 = scope.get("?"); + let w6 = scope.get("a"); + let w7 = scope.get("this"); + let w8 = scope.get("this.canada"); + let w9 = scope.get("[1, 2, 3]"); + let w10 = scope.get("x = [1, 2, 3]"); + let w11 = scope.get("y = [1, 2, 3]; y.test = 4"); + let w12 = scope.get("z = [1, 2, 3]; z.test = 4; z"); + let w13 = scope.get("t = [1, 2, 3]; t.test = 4; !t"); + let w14 = scope.get("arguments[0]"); + let w15 = scope.get("encodeURI(\"\\\")"); + let w16 = scope.get("decodeURI(\"\\\")"); + let w17 = scope.get("decodeURIComponent(\"%\")"); + let w18 = scope.get("//"); + let w19 = scope.get("// 42"); + let w20 = scope.get("{}.foo"); + let w21 = scope.get("{}.foo()"); + let w22 = scope.get("({}).foo()"); + let w23 = scope.get("new Array(-1)"); + let w24 = scope.get("4.2.toExponential(-4.2)"); + let w25 = scope.get("throw new Error(\"bazinga\")"); + let w26 = scope.get("({ get error() { throw new Error(\"bazinga\") } }).error"); + let w27 = scope.get("throw { get name() { throw \"bazinga\" } }"); + let w28 = scope.get("'$$$'.replace(/\\$/, 'foo')"); + + ok(w1, "The first watch expression should be present in the scope."); + ok(w2, "The second watch expression should be present in the scope."); + ok(w3, "The third watch expression should be present in the scope."); + ok(w4, "The fourth watch expression should be present in the scope."); + ok(w5, "The fifth watch expression should be present in the scope."); + ok(w6, "The sixth watch expression should be present in the scope."); + ok(w7, "The seventh watch expression should be present in the scope."); + ok(w8, "The eight watch expression should be present in the scope."); + ok(w9, "The ninth watch expression should be present in the scope."); + ok(w10, "The tenth watch expression should be present in the scope."); + ok(w11, "The eleventh watch expression should be present in the scope."); + ok(w12, "The twelfth watch expression should be present in the scope."); + ok(w13, "The 13th watch expression should be present in the scope."); + ok(w14, "The 14th watch expression should be present in the scope."); + ok(w15, "The 15th watch expression should be present in the scope."); + ok(w16, "The 16th watch expression should be present in the scope."); + ok(w17, "The 17th watch expression should be present in the scope."); + ok(w18, "The 18th watch expression should be present in the scope."); + ok(w19, "The 19th watch expression should be present in the scope."); + ok(w20, "The 20th watch expression should be present in the scope."); + ok(w21, "The 21st watch expression should be present in the scope."); + ok(w22, "The 22nd watch expression should be present in the scope."); + ok(w23, "The 23nd watch expression should be present in the scope."); + ok(w24, "The 24th watch expression should be present in the scope."); + ok(w25, "The 25th watch expression should be present in the scope."); + ok(w26, "The 26th watch expression should be present in the scope."); + ok(!w27, "The 27th watch expression should not be present in the scope."); + ok(w28, "The 28th watch expression should be present in the scope."); + + is(w1.value, "a", "The first value is correct."); + is(w2.value, "a", "The second value is correct."); + is(w3.value, "a\"\"", "The third value is correct."); + is(w4.value, "a''", "The fourth value is correct."); + is(w5.value, "SyntaxError: expected expression, got '?'", "The fifth value is correct."); + + if (typeof expected_a == "object") { + is(w6.value.type, expected_a.type, "The sixth value type is correct."); + is(w6.value.class, expected_a.class, "The sixth value class is correct."); + } else { + is(w6.value, expected_a, "The sixth value is correct."); + } + + if (typeof expected_this == "object") { + is(w7.value.type, expected_this.type, "The seventh value type is correct."); + is(w7.value.class, expected_this.class, "The seventh value class is correct."); + } else { + is(w7.value, expected_this, "The seventh value is correct."); + } + + if (typeof expected_prop == "object") { + is(w8.value.type, expected_prop.type, "The eighth value type is correct."); + is(w8.value.class, expected_prop.class, "The eighth value class is correct."); + } else { + is(w8.value, expected_prop, "The eighth value is correct."); + } + + is(w9.value.type, "object", "The ninth value type is correct."); + is(w9.value.class, "Array", "The ninth value class is correct."); + is(w10.value.type, "object", "The tenth value type is correct."); + is(w10.value.class, "Array", "The tenth value class is correct."); + is(w11.value, "4", "The eleventh value is correct."); + is(w12.value.type, "object", "The eleventh value type is correct."); + is(w12.value.class, "Array", "The twelfth value class is correct."); + is(w13.value, false, "The 13th value is correct."); + + if (typeof expected_args == "object") { + is(w14.value.type, expected_args.type, "The 14th value type is correct."); + is(w14.value.class, expected_args.class, "The 14th value class is correct."); + } else { + is(w14.value, expected_args, "The 14th value is correct."); + } + + is(w15.value, "SyntaxError: unterminated string literal", "The 15th value is correct."); + is(w16.value, "SyntaxError: unterminated string literal", "The 16th value is correct."); + is(w17.value, "URIError: malformed URI sequence", "The 17th value is correct."); + + is(w18.value.type, "undefined", "The 18th value type is correct."); + is(w18.value.class, undefined, "The 18th value class is correct."); + + is(w19.value.type, "undefined", "The 19th value type is correct."); + is(w19.value.class, undefined, "The 19th value class is correct."); + + is(w20.value, "SyntaxError: expected expression, got '.'", "The 20th value is correct."); + is(w21.value, "SyntaxError: expected expression, got '.'", "The 21th value is correct."); + is(w22.value, "TypeError: (intermediate value).foo is not a function", "The 22th value is correct."); + is(w23.value, "RangeError: invalid array length", "The 23th value is correct."); + is(w24.value, "RangeError: precision -4 out of range", "The 24th value is correct."); + is(w25.value, "Error: bazinga", "The 25th value is correct."); + is(w26.value, "Error: bazinga", "The 26th value is correct."); + is(w28.value, "foo$$", "The 28th value is correct."); + } +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-01.js b/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-01.js new file mode 100644 index 000000000..72b0cbd16 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-01.js @@ -0,0 +1,21 @@ +// Check to make sure that a worker can be attached to a toolbox +// and that the console works. + +var TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html"; +var WORKER_URL = "code_WorkerActor.attachThread-worker.js"; + +add_task(function* testNormalExecution() { + let {client, tab, tabClient, workerClient, toolbox, gDebugger} = + yield initWorkerDebugger(TAB_URL, WORKER_URL); + + let jsterm = yield getSplitConsole(toolbox); + let executed = yield jsterm.execute("this.location.toString()"); + ok(executed.textContent.includes(WORKER_URL), + "Evaluating the global's location works"); + + terminateWorkerInTab(tab, WORKER_URL); + yield waitForWorkerClose(workerClient); + yield gDevTools.closeToolbox(TargetFactory.forWorker(workerClient)); + yield close(client); + yield removeTab(tab); +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-02.js b/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-02.js new file mode 100644 index 000000000..b6e8d12af --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-02.js @@ -0,0 +1,58 @@ +// Check to make sure that a worker can be attached to a toolbox +// and that the console works. + +var TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html"; +var WORKER_URL = "code_WorkerActor.attachThread-worker.js"; + +add_task(function* testWhilePaused() { + let {client, tab, tabClient, workerClient, toolbox, gDebugger} = + yield initWorkerDebugger(TAB_URL, WORKER_URL); + + let gTarget = gDebugger.gTarget; + let gResumeButton = gDebugger.document.getElementById("resume"); + let gResumeKey = gDebugger.document.getElementById("resumeKey"); + + // Execute some basic math to make sure evaluations are working. + let jsterm = yield getSplitConsole(toolbox); + let executed = yield jsterm.execute("10000+1"); + ok(executed.textContent.includes("10001"), "Text for message appeared correct"); + + // Pause the worker by waiting for next execution and then sending a message to + // it from the main thread. + let oncePaused = gTarget.once("thread-paused"); + EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger); + once(gDebugger.gClient, "willInterrupt").then(() => { + info("Posting message to worker, then waiting for a pause"); + postMessageToWorkerInTab(tab, WORKER_URL, "ping"); + }); + yield oncePaused; + + let command1 = jsterm.execute("10000+2"); + let command2 = jsterm.execute("10000+3"); + let command3 = jsterm.execute("foobar"); // throw an error + + info("Trying to get the result of command1"); + executed = yield command1; + ok(executed.textContent.includes("10002"), + "command1 executed successfully"); + + info("Trying to get the result of command2"); + executed = yield command2; + ok(executed.textContent.includes("10003"), + "command2 executed successfully"); + + info("Trying to get the result of command3"); + executed = yield command3; + ok(executed.textContent.includes("ReferenceError: foobar is not defined"), + "command3 executed successfully"); + + let onceResumed = gTarget.once("thread-resumed"); + EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger); + yield onceResumed; + + terminateWorkerInTab(tab, WORKER_URL); + yield waitForWorkerClose(workerClient); + yield gDevTools.closeToolbox(TargetFactory.forWorker(workerClient)); + yield close(client); + yield removeTab(tab); +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-03.js b/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-03.js new file mode 100644 index 000000000..49821492f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-console-03.js @@ -0,0 +1,46 @@ +// Check to make sure that a worker can be attached to a toolbox +// and that the console works. + +var TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html"; +var WORKER_URL = "code_WorkerActor.attachThread-worker.js"; + +// Test to see if creating the pause from the console works. +add_task(function* testPausedByConsole() { + let {client, tab, tabClient, workerClient, toolbox, gDebugger} = + yield initWorkerDebugger(TAB_URL, WORKER_URL); + + let gTarget = gDebugger.gTarget; + let gResumeButton = gDebugger.document.getElementById("resume"); + let gResumeKey = gDebugger.document.getElementById("resumeKey"); + + let jsterm = yield getSplitConsole(toolbox); + let executed = yield jsterm.execute("10000+1"); + ok(executed.textContent.includes("10001"), + "Text for message appeared correct"); + + let oncePaused = gTarget.once("thread-paused"); + EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger); + let pausedExecution = jsterm.execute("10000+2"); + + info("Executed a command with 'break on next' active, waiting for pause"); + yield oncePaused; + + executed = yield jsterm.execute("10000+3"); + ok(executed.textContent.includes("10003"), + "Text for message appeared correct"); + + info("Waiting for a resume"); + let onceResumed = gTarget.once("thread-resumed"); + EventUtils.sendMouseEvent({ type: "mousedown" }, gResumeButton, gDebugger); + yield onceResumed; + + executed = yield pausedExecution; + ok(executed.textContent.includes("10002"), + "Text for message appeared correct"); + + terminateWorkerInTab(tab, WORKER_URL); + yield waitForWorkerClose(workerClient); + yield gDevTools.closeToolbox(TargetFactory.forWorker(workerClient)); + yield close(client); + yield removeTab(tab); +}); diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_worker-source-map.js b/devtools/client/debugger/test/mochitest/browser_dbg_worker-source-map.js new file mode 100644 index 000000000..c4e8841d5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-source-map.js @@ -0,0 +1,89 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const TAB_URL = EXAMPLE_URL + "doc_worker-source-map.html"; +const WORKER_URL = "code_worker-source-map.js"; +const COFFEE_URL = EXAMPLE_URL + "code_worker-source-map.coffee"; + +function selectWorker(aPanel, aURL) { + let panelWin = aPanel.panelWin; + let promise = waitForDebuggerEvents(aPanel, panelWin.EVENTS.WORKER_SELECTED); + let Workers = panelWin.DebuggerView.Workers; + let item = Workers.getItemForAttachment((workerForm) => { + return workerForm.url === aURL; + }); + Workers.selectedItem = item; + return promise; +} + +function test() { + return Task.spawn(function* () { + yield pushPrefs(["devtools.debugger.workers", true]); + + let options = { + source: TAB_URL, + line: 1 + }; + let [tab,, panel] = yield initDebugger(TAB_URL, options); + let toolbox = yield selectWorker(panel, WORKER_URL); + let workerPanel = toolbox.getCurrentPanel(); + yield waitForSourceShown(workerPanel, ".coffee"); + let panelWin = workerPanel.panelWin; + let Sources = panelWin.DebuggerView.Sources; + let editor = panelWin.DebuggerView.editor; + let threadClient = panelWin.gThreadClient; + + isnot(Sources.selectedItem.attachment.source.url.indexOf(".coffee"), -1, + "The debugger should show the source mapped coffee source file."); + is(Sources.selectedValue.indexOf(".js"), -1, + "The debugger should not show the generated js source file."); + is(editor.getText().indexOf("isnt"), 211, + "The debugger's editor should have the coffee source source displayed."); + is(editor.getText().indexOf("function"), -1, + "The debugger's editor should not have the JS source displayed."); + + yield threadClient.interrupt(); + let sourceForm = getSourceForm(Sources, COFFEE_URL); + let source = threadClient.source(sourceForm); + let response = yield source.setBreakpoint({ line: 5 }); + + ok(!response.error, + "Should be able to set a breakpoint in a coffee source file."); + ok(!response.actualLocation, + "Should be able to set a breakpoint on line 5."); + + let promise = new Promise((resolve) => { + threadClient.addOneTimeListener("paused", (event, packet) => { + is(packet.type, "paused", + "We should now be paused again."); + is(packet.why.type, "breakpoint", + "and the reason we should be paused is because we hit a breakpoint."); + + // Check that we stopped at the right place, by making sure that the + // environment is in the state that we expect. + is(packet.frame.environment.bindings.variables.start.value, 0, + "'start' is 0."); + is(packet.frame.environment.bindings.variables.stop.value.type, "undefined", + "'stop' hasn't been assigned to yet."); + is(packet.frame.environment.bindings.variables.pivot.value.type, "undefined", + "'pivot' hasn't been assigned to yet."); + + waitForCaretUpdated(workerPanel, 5).then(resolve); + }); + }); + + // This will cause the breakpoint to be hit, and put us back in the + // paused state. + yield threadClient.resume(); + callInTab(tab, "binary_search", [0, 2, 3, 5, 7, 10], 5); + yield promise; + + yield threadClient.resume(); + yield toolbox.destroy(); + yield closeDebuggerAndFinish(panel); + + yield popPrefs(); + }); +} diff --git a/devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js b/devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js new file mode 100644 index 000000000..46198d31c --- /dev/null +++ b/devtools/client/debugger/test/mochitest/browser_dbg_worker-window.js @@ -0,0 +1,61 @@ +// Check to make sure that a worker can be attached to a toolbox +// directly, and that the toolbox has expected properties. + +"use strict"; + +// Whitelisting this test. +// As part of bug 1077403, the leaking uncaught rejections should be fixed. +thisTestLeaksUncaughtRejectionsAndShouldBeFixed("[object Object]"); + +var TAB_URL = EXAMPLE_URL + "doc_WorkerActor.attachThread-tab.html"; +var WORKER_URL = "code_WorkerActor.attachThread-worker.js"; + +add_task(function* () { + yield pushPrefs(["devtools.scratchpad.enabled", true]); + + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let tab = yield addTab(TAB_URL); + let { tabs } = yield listTabs(client); + let [, tabClient] = yield attachTab(client, findTab(tabs, TAB_URL)); + + yield listWorkers(tabClient); + yield createWorkerInTab(tab, WORKER_URL); + + let { workers } = yield listWorkers(tabClient); + let [, workerClient] = yield attachWorker(tabClient, + findWorker(workers, WORKER_URL)); + + let toolbox = yield gDevTools.showToolbox(TargetFactory.forWorker(workerClient), + "jsdebugger", + Toolbox.HostType.WINDOW); + + is(toolbox.hostType, "window", "correct host"); + + yield new Promise(done => { + toolbox.win.parent.addEventListener("message", function onmessage(event) { + if (event.data.name == "set-host-title") { + toolbox.win.parent.removeEventListener("message", onmessage); + done(); + } + }); + }); + ok(toolbox.win.parent.document.title.includes(WORKER_URL), + "worker URL in host title"); + + let toolTabs = toolbox.doc.querySelectorAll(".devtools-tab"); + let activeTools = [...toolTabs].map(tab=>tab.getAttribute("toolid")); + + is(activeTools.join(","), "webconsole,jsdebugger,scratchpad,options", + "Correct set of tools supported by worker"); + + terminateWorkerInTab(tab, WORKER_URL); + yield waitForWorkerClose(workerClient); + yield close(client); + + yield toolbox.destroy(); +}); diff --git a/devtools/client/debugger/test/mochitest/code_WorkerActor.attach-worker1.js b/devtools/client/debugger/test/mochitest/code_WorkerActor.attach-worker1.js new file mode 100644 index 000000000..18f14864d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_WorkerActor.attach-worker1.js @@ -0,0 +1,5 @@ +"use strict"; + +self.onmessage = function () {}; + +postMessage("load"); diff --git a/devtools/client/debugger/test/mochitest/code_WorkerActor.attach-worker2.js b/devtools/client/debugger/test/mochitest/code_WorkerActor.attach-worker2.js new file mode 100644 index 000000000..18f14864d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_WorkerActor.attach-worker2.js @@ -0,0 +1,5 @@ +"use strict"; + +self.onmessage = function () {}; + +postMessage("load"); diff --git a/devtools/client/debugger/test/mochitest/code_WorkerActor.attachThread-worker.js b/devtools/client/debugger/test/mochitest/code_WorkerActor.attachThread-worker.js new file mode 100644 index 000000000..881eab0b8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_WorkerActor.attachThread-worker.js @@ -0,0 +1,16 @@ +"use strict"; + +function f() { + var a = 1; + var b = 2; + var c = 3; +} + +self.onmessage = function (event) { + if (event.data == "ping") { + f(); + postMessage("pong"); + } +}; + +postMessage("load"); diff --git a/devtools/client/debugger/test/mochitest/code_binary_search.coffee b/devtools/client/debugger/test/mochitest/code_binary_search.coffee new file mode 100644 index 000000000..e3dacdaaa --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_binary_search.coffee @@ -0,0 +1,18 @@ +# Uses a binary search algorithm to locate a value in the specified array. +window.binary_search = (items, value) -> + + start = 0 + stop = items.length - 1 + pivot = Math.floor (start + stop) / 2 + + while items[pivot] isnt value and start < stop + + # Adjust the search area. + stop = pivot - 1 if value < items[pivot] + start = pivot + 1 if value > items[pivot] + + # Recalculate the pivot. + pivot = Math.floor (stop + start) / 2 + + # Make sure we've found the correct value. + if items[pivot] is value then pivot else -1
\ No newline at end of file diff --git a/devtools/client/debugger/test/mochitest/code_binary_search.js b/devtools/client/debugger/test/mochitest/code_binary_search.js new file mode 100644 index 000000000..c43848a60 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_binary_search.js @@ -0,0 +1,29 @@ +// Generated by CoffeeScript 1.6.1 +(function() { + + window.binary_search = function(items, value) { + var pivot, start, stop; + start = 0; + stop = items.length - 1; + pivot = Math.floor((start + stop) / 2); + while (items[pivot] !== value && start < stop) { + if (value < items[pivot]) { + stop = pivot - 1; + } + if (value > items[pivot]) { + start = pivot + 1; + } + pivot = Math.floor((stop + start) / 2); + } + if (items[pivot] === value) { + return pivot; + } else { + return -1; + } + }; + +}).call(this); + +/* +//# sourceMappingURL=code_binary_search.map +*/ diff --git a/devtools/client/debugger/test/mochitest/code_binary_search.map b/devtools/client/debugger/test/mochitest/code_binary_search.map new file mode 100644 index 000000000..8d2251125 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_binary_search.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "file": "code_binary_search.js", + "sourceRoot": "", + "sources": [ + "code_binary_search.coffee" + ], + "names": [], + "mappings": ";AACA;CAAA;CAAA,CAAA,CAAuB,EAAA,CAAjB,GAAkB,IAAxB;CAEE,OAAA,UAAA;CAAA,EAAQ,CAAR,CAAA;CAAA,EACQ,CAAR,CAAa,CAAL;CADR,EAEQ,CAAR,CAAA;CAEA,EAA0C,CAAR,CAAtB,MAAN;CAGJ,EAA6B,CAAR,CAAA,CAArB;CAAA,EAAQ,CAAR,CAAQ,GAAR;QAAA;CACA,EAA6B,CAAR,CAAA,CAArB;CAAA,EAAQ,EAAR,GAAA;QADA;CAAA,EAIQ,CAAI,CAAZ,CAAA;CAXF,IAIA;CAUA,GAAA,CAAS;CAAT,YAA8B;MAA9B;AAA0C,CAAD,YAAA;MAhBpB;CAAvB,EAAuB;CAAvB" +} diff --git a/devtools/client/debugger/test/mochitest/code_blackboxing_blackboxme.js b/devtools/client/debugger/test/mochitest/code_blackboxing_blackboxme.js new file mode 100644 index 000000000..713b3d50d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_blackboxing_blackboxme.js @@ -0,0 +1,9 @@ +function blackboxme(fn) { + (function one() { + (function two() { + (function three() { + fn(); + }()); + }()); + }()); +} diff --git a/devtools/client/debugger/test/mochitest/code_blackboxing_one.js b/devtools/client/debugger/test/mochitest/code_blackboxing_one.js new file mode 100644 index 000000000..7f37b02ad --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_blackboxing_one.js @@ -0,0 +1,4 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function one() { two(); } diff --git a/devtools/client/debugger/test/mochitest/code_blackboxing_three.js b/devtools/client/debugger/test/mochitest/code_blackboxing_three.js new file mode 100644 index 000000000..55ed6c4da --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_blackboxing_three.js @@ -0,0 +1,4 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function three() { doDebuggerStatement(); } diff --git a/devtools/client/debugger/test/mochitest/code_blackboxing_two.js b/devtools/client/debugger/test/mochitest/code_blackboxing_two.js new file mode 100644 index 000000000..4790ea4a7 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_blackboxing_two.js @@ -0,0 +1,4 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function two() { three(); } diff --git a/devtools/client/debugger/test/mochitest/code_blackboxing_unblackbox.min.js b/devtools/client/debugger/test/mochitest/code_blackboxing_unblackbox.min.js new file mode 100644 index 000000000..b8b285589 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_blackboxing_unblackbox.min.js @@ -0,0 +1 @@ +function blackboxme() {one();} function one() {} diff --git a/devtools/client/debugger/test/mochitest/code_breakpoints-break-on-last-line-of-script-on-reload.js b/devtools/client/debugger/test/mochitest/code_breakpoints-break-on-last-line-of-script-on-reload.js new file mode 100644 index 000000000..839f22883 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_breakpoints-break-on-last-line-of-script-on-reload.js @@ -0,0 +1,6 @@ +debugger; +var a = (function () { + var b = 9; + console.log("x", b); + return b; +})(); diff --git a/devtools/client/debugger/test/mochitest/code_breakpoints-other-tabs.js b/devtools/client/debugger/test/mochitest/code_breakpoints-other-tabs.js new file mode 100644 index 000000000..2cf53ba2d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_breakpoints-other-tabs.js @@ -0,0 +1,4 @@ +function testCase() { + var foo = "break on me"; + debugger; +} diff --git a/devtools/client/debugger/test/mochitest/code_bug-896139.js b/devtools/client/debugger/test/mochitest/code_bug-896139.js new file mode 100644 index 000000000..65313bcb2 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_bug-896139.js @@ -0,0 +1,8 @@ +<!-- Any copyright is dedicated to the Public Domain. + http:// creativecommons.org/publicdomain/zero/1.0/ --> + +function f() { + var a = 1; + var b = 2; + var c = 3; +} diff --git a/devtools/client/debugger/test/mochitest/code_frame-script.js b/devtools/client/debugger/test/mochitest/code_frame-script.js new file mode 100644 index 000000000..35a950b01 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_frame-script.js @@ -0,0 +1,106 @@ +"use strict"; + +var { classes: Cc, interfaces: Ci, utils: Cu } = Components; +const { loadSubScript } = Cc["@mozilla.org/moz/jssubscript-loader;1"]. + getService(Ci.mozIJSSubScriptLoader); + +// Set up a dummy environment so that EventUtils works. We need to be careful to +// pass a window object into each EventUtils method we call rather than having +// it rely on the |window| global. +let EventUtils = {}; +EventUtils.window = content; +EventUtils.parent = EventUtils.window; +EventUtils._EU_Ci = Components.interfaces; +EventUtils._EU_Cc = Components.classes; +EventUtils.navigator = content.navigator; +EventUtils.KeyboardEvent = content.KeyboardEvent; +loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils); + +dump("Frame script loaded.\n"); + +var workers = {}; + +this.call = function (name, args) { + dump("Calling function with name " + name + ".\n"); + + dump("args " + JSON.stringify(args) + "\n"); + return XPCNativeWrapper.unwrap(content)[name].apply(undefined, args); +}; + +this._eval = function (string) { + dump("Evalling string.\n"); + + return content.eval(string); +}; + +this.generateMouseClick = function (path) { + dump("Generating mouse click.\n"); + + let target = eval(path); + EventUtils.synthesizeMouseAtCenter(target, {}, + target.ownerDocument.defaultView); +}; + +this.createWorker = function (url) { + dump("Creating worker with url '" + url + "'.\n"); + + return new Promise(function (resolve, reject) { + let worker = new content.Worker(url); + worker.addEventListener("message", function listener() { + worker.removeEventListener("message", listener); + workers[url] = worker; + resolve(); + }); + }); +}; + +this.terminateWorker = function (url) { + dump("Terminating worker with url '" + url + "'.\n"); + + workers[url].terminate(); + delete workers[url]; +}; + +this.postMessageToWorker = function (url, message) { + dump("Posting message to worker with url '" + url + "'.\n"); + + return new Promise(function (resolve) { + let worker = workers[url]; + worker.postMessage(message); + worker.addEventListener("message", function listener() { + worker.removeEventListener("message", listener); + resolve(); + }); + }); +}; + +addMessageListener("jsonrpc", function ({ data: { method, params, id } }) { + method = this[method]; + Promise.resolve().then(function () { + return method.apply(undefined, params); + }).then(function (result) { + sendAsyncMessage("jsonrpc", { + result: result, + error: null, + id: id + }); + }, function (error) { + sendAsyncMessage("jsonrpc", { + result: null, + error: error.message.toString(), + id: id + }); + }); +}); + +addMessageListener("test:postMessageToWorker", function (message) { + dump("Posting message '" + message.data.message + "' to worker with url '" + + message.data.url + "'.\n"); + + let worker = workers[message.data.url]; + worker.postMessage(message.data.message); + worker.addEventListener("message", function listener() { + worker.removeEventListener("message", listener); + sendAsyncMessage("test:postMessageToWorker"); + }); +}); diff --git a/devtools/client/debugger/test/mochitest/code_function-jump-01.js b/devtools/client/debugger/test/mochitest/code_function-jump-01.js new file mode 100644 index 000000000..d71cbfa93 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_function-jump-01.js @@ -0,0 +1,6 @@ + +function foo() { + // some function +} + +foo(); diff --git a/devtools/client/debugger/test/mochitest/code_function-search-01.js b/devtools/client/debugger/test/mochitest/code_function-search-01.js new file mode 100644 index 000000000..77331d722 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_function-search-01.js @@ -0,0 +1,42 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function test() { + // Blah! First source! +} + +test.prototype = { + anonymousExpression: function () { + }, + namedExpression: function NAME() { + }, + sub: { + sub: { + sub: { + } + } + } +}; + +var foo = { + a_test: function () { + }, + n_test: function x() { + }, + sub: { + a_test: function () { + }, + n_test: function y() { + }, + sub: { + a_test: function () { + }, + n_test: function z() { + }, + sub: { + test_SAME_NAME: function test_SAME_NAME() { + } + } + } + } +}; diff --git a/devtools/client/debugger/test/mochitest/code_function-search-02.js b/devtools/client/debugger/test/mochitest/code_function-search-02.js new file mode 100644 index 000000000..ab25641d2 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_function-search-02.js @@ -0,0 +1,21 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +var test2 = function () { + // Blah! Second source! +}; + +var test3 = function test3_NAME() { +}; + +var test4_SAME_NAME = function test4_SAME_NAME() { +}; + +test.prototype.x = function X() { +}; +test.prototype.sub.y = function Y() { +}; +test.prototype.sub.sub.z = function Z() { +}; +test.prototype.sub.sub.sub.t = this.x = this.y = this.z = function () { +}; diff --git a/devtools/client/debugger/test/mochitest/code_function-search-03.js b/devtools/client/debugger/test/mochitest/code_function-search-03.js new file mode 100644 index 000000000..e64292a92 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_function-search-03.js @@ -0,0 +1,32 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +window.addEventListener("bogus", function namedEventListener() { + // Blah! Third source! +}); + +try { + var bar = foo.sub.sub.test({ + a: function A() { + } + }); + + bar.alpha = foo.sub.sub.test({ + b: function B() { + } + }); + + bar.alpha.beta = new X(Y(Z(foo.sub.sub.test({ + c: function C() { + } + })))); + + this.theta = new X(new Y(new Z(new foo.sub.sub.test({ + d: function D() { + } + })))); + + var fun = foo = bar = this.t_foo = window.w_bar = function baz() {}; + +} catch (e) { +} diff --git a/devtools/client/debugger/test/mochitest/code_listworkers-worker1.js b/devtools/client/debugger/test/mochitest/code_listworkers-worker1.js new file mode 100644 index 000000000..8cee6809e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_listworkers-worker1.js @@ -0,0 +1,3 @@ +"use strict"; + +self.onmessage = function () {}; diff --git a/devtools/client/debugger/test/mochitest/code_listworkers-worker2.js b/devtools/client/debugger/test/mochitest/code_listworkers-worker2.js new file mode 100644 index 000000000..8cee6809e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_listworkers-worker2.js @@ -0,0 +1,3 @@ +"use strict"; + +self.onmessage = function () {}; diff --git a/devtools/client/debugger/test/mochitest/code_location-changes.js b/devtools/client/debugger/test/mochitest/code_location-changes.js new file mode 100644 index 000000000..d164b8bdf --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_location-changes.js @@ -0,0 +1,7 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function myFunction() { + var a = 1; + debugger; +} diff --git a/devtools/client/debugger/test/mochitest/code_math.js b/devtools/client/debugger/test/mochitest/code_math.js new file mode 100644 index 000000000..f765817bb --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_math.js @@ -0,0 +1,45 @@ +function add(a, b, k) { + var result = a + b; + return k(result); +} + +function sub(a, b, k) { + var result = a - b; + return k(result); +} + +function mul(a, b, k) { + var result = a * b; + return k(result); +} + +function div(a, b, k) { + var result = a / b; + return k(result); +} + +function arithmetic() { + add(4, 4, function (a) { + // 8 + sub(a, 2, function (b) { + // 6 + mul(b, 3, function (c) { + // 18 + div(c, 2, function (d) { + // 9 + console.log(d); + }); + }); + }); + }); +} + +// Compile with closure compiler and the following flags: +// +// --compilation_level WHITESPACE_ONLY +// --source_map_format V3 +// --create_source_map code_math.map +// --js_output_file code_math.min.js +// +// And then append the sourceMappingURL comment directive to code_math.min.js +// manually. diff --git a/devtools/client/debugger/test/mochitest/code_math.map b/devtools/client/debugger/test/mochitest/code_math.map new file mode 100644 index 000000000..474304c39 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_math.map @@ -0,0 +1,8 @@ +{ +"version":3, +"file":"code_math.min.js", +"lineCount":1, +"mappings":"AAAAA,QAASA,IAAG,CAACC,CAAD,CAAIC,CAAJ,CAAOC,CAAP,CAAU,CACpB,IAAIC,OAASH,CAATG,CAAaF,CACjB,OAAOC,EAAA,CAAEC,MAAF,CAFa,CAKtBC,QAASA,IAAG,CAACJ,CAAD,CAAIC,CAAJ,CAAOC,CAAP,CAAU,CACpB,IAAIC,OAASH,CAATG,CAAaF,CACjB,OAAOC,EAAA,CAAEC,MAAF,CAFa,CAKtBE,QAASA,IAAG,CAACL,CAAD,CAAIC,CAAJ,CAAOC,CAAP,CAAU,CACpB,IAAIC,OAASH,CAATG,CAAaF,CACjB,OAAOC,EAAA,CAAEC,MAAF,CAFa,CAKtBG,QAASA,IAAG,CAACN,CAAD,CAAIC,CAAJ,CAAOC,CAAP,CAAU,CACpB,IAAIC,OAASH,CAATG,CAAaF,CACjB,OAAOC,EAAA,CAAEC,MAAF,CAFa,CAKtBI,QAASA,WAAU,EAAG,CACpBR,GAAA,CAAI,CAAJ,CAAO,CAAP,CAAU,QAAS,CAACC,CAAD,CAAI,CAErBI,GAAA,CAAIJ,CAAJ,CAAO,CAAP,CAAU,QAAS,CAACC,CAAD,CAAI,CAErBI,GAAA,CAAIJ,CAAJ,CAAO,CAAP,CAAU,QAAS,CAACO,CAAD,CAAI,CAErBF,GAAA,CAAIE,CAAJ,CAAO,CAAP,CAAU,QAAS,CAACC,CAAD,CAAI,CAErBC,OAAAC,IAAA,CAAYF,CAAZ,CAFqB,CAAvB,CAFqB,CAAvB,CAFqB,CAAvB,CAFqB,CAAvB,CADoB;", +"sources":["code_math.js"], +"names":["add","a","b","k","result","sub","mul","div","arithmetic","c","d","console","log"] +} diff --git a/devtools/client/debugger/test/mochitest/code_math.min.js b/devtools/client/debugger/test/mochitest/code_math.min.js new file mode 100644 index 000000000..7d1fb48f0 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_math.min.js @@ -0,0 +1,2 @@ +function add(a,b,k){var result=a+b;return k(result)}function sub(a,b,k){var result=a-b;return k(result)}function mul(a,b,k){var result=a*b;return k(result)}function div(a,b,k){var result=a/b;return k(result)}function arithmetic(){add(4,4,function(a){sub(a,2,function(b){mul(b,3,function(c){div(c,2,function(d){console.log(d)})})})})}; +//@ sourceMappingURL=code_math.map diff --git a/devtools/client/debugger/test/mochitest/code_math_bogus_map.js b/devtools/client/debugger/test/mochitest/code_math_bogus_map.js new file mode 100644 index 000000000..82e156b10 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_math_bogus_map.js @@ -0,0 +1,4 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +function stopMe(){throw Error("boom");}try{stopMe();var a=1;a=a*2;}catch(e){}; +//# sourceMappingURL=bogus.map diff --git a/devtools/client/debugger/test/mochitest/code_same-line-functions.js b/devtools/client/debugger/test/mochitest/code_same-line-functions.js new file mode 100644 index 000000000..60a6c6ab1 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_same-line-functions.js @@ -0,0 +1 @@ +function first() { var a = "first"; second(); function second() { var a = "second"; } } diff --git a/devtools/client/debugger/test/mochitest/code_script-eval.js b/devtools/client/debugger/test/mochitest/code_script-eval.js new file mode 100644 index 000000000..0d7ceba66 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_script-eval.js @@ -0,0 +1,14 @@ + +var bar; + +function evalSource() { + eval("bar = function() {\nvar x = 5;\n}"); +} + +function evalSourceWithSourceURL() { + eval("bar = function() {\nvar x = 6;\n} //# sourceURL=bar.js"); +} + +function evalSourceWithDebugger() { + eval("bar = function() {\nvar x = 7;\ndebugger; }\n bar();"); +} diff --git a/devtools/client/debugger/test/mochitest/code_script-switching-01.js b/devtools/client/debugger/test/mochitest/code_script-switching-01.js new file mode 100644 index 000000000..4ba2772de --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_script-switching-01.js @@ -0,0 +1,6 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function firstCall() { + secondCall(); +} diff --git a/devtools/client/debugger/test/mochitest/code_script-switching-02.js b/devtools/client/debugger/test/mochitest/code_script-switching-02.js new file mode 100644 index 000000000..feb74315f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_script-switching-02.js @@ -0,0 +1,13 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function secondCall() { + // This comment is useful: ☺ + debugger; + function foo() {} + if (x) { + foo(); + } +} + +var x = true; diff --git a/devtools/client/debugger/test/mochitest/code_test-editor-mode b/devtools/client/debugger/test/mochitest/code_test-editor-mode new file mode 100644 index 000000000..ca8a90889 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_test-editor-mode @@ -0,0 +1,6 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function secondCall() { + debugger; +} diff --git a/devtools/client/debugger/test/mochitest/code_ugly-2.js b/devtools/client/debugger/test/mochitest/code_ugly-2.js new file mode 100644 index 000000000..15fba0701 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_ugly-2.js @@ -0,0 +1 @@ +function main2() { var a = 1 + 3; var b = a++; return b + a; } diff --git a/devtools/client/debugger/test/mochitest/code_ugly-3.js b/devtools/client/debugger/test/mochitest/code_ugly-3.js new file mode 100644 index 000000000..0424b288c --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_ugly-3.js @@ -0,0 +1 @@ +function main3() { var a = 1; debugger; noop(a); return 10; }; diff --git a/devtools/client/debugger/test/mochitest/code_ugly-4.js b/devtools/client/debugger/test/mochitest/code_ugly-4.js new file mode 100644 index 000000000..dbd596dc3 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_ugly-4.js @@ -0,0 +1,25 @@ +function a(){b()}function b(){debugger} +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWJjLmpzIiwic291cmNlcyI6WyJkYXRhOnRleHQvamF2YXNjcmlwdCxmdW5jdGlvbiBhKCl7YigpfSIsImRhdGE6dGV4dC9qYXZhc2NyaXB0LGZ1bmN0aW9uIGIoKXtkZWJ1Z2dlcn0iXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsaUJDQUEsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMifQ== + +// Generate this file by evaluating the following in a browser-environment +// scratchpad: +// +// let { require } = Components.utils.import('resource://devtools/shared/Loader.jsm', {}); +// let { SourceNode } = require("source-map"); +// +// let dataUrl = s => "data:text/javascript," + s; +// +// let A = "function a(){b()}"; +// let A_URL = dataUrl(A); +// let B = "function b(){debugger}"; +// let B_URL = dataUrl(B); +// +// let result = (new SourceNode(null, null, null, [ +// new SourceNode(1, 0, A_URL, A), +// B.split("").map((ch, i) => new SourceNode(1, i, B_URL, ch)) +// ])).toStringWithSourceMap({ +// file: "abc.js" +// }); +// +// result.code + "\n//# " + "sourceMappingURL=data:application/json;base64," + btoa(JSON.stringify(result.map)); + diff --git a/devtools/client/debugger/test/mochitest/code_ugly-5.js b/devtools/client/debugger/test/mochitest/code_ugly-5.js new file mode 100644 index 000000000..a94f521dc --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_ugly-5.js @@ -0,0 +1,14 @@ +/*1385419625,181944095,JIT Construction: v1021776,en_US*/ +/** + * Copyright Test Inc. + * + * Licensed under the Apache License, Version 2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + */ +// Copyright Test Inc. +// +// etc... +// etc... +function foo(){var a=1;var b=2;bar(a,b);} +function bar(c,d){return 3;} +foo(); diff --git a/devtools/client/debugger/test/mochitest/code_ugly-6.js b/devtools/client/debugger/test/mochitest/code_ugly-6.js new file mode 100644 index 000000000..0c678c140 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_ugly-6.js @@ -0,0 +1,5 @@ +// Copyright Test Inc. +// +// etc... +// etc... +function main(){ return 0; }
\ No newline at end of file diff --git a/devtools/client/debugger/test/mochitest/code_ugly-7.js b/devtools/client/debugger/test/mochitest/code_ugly-7.js new file mode 100644 index 000000000..8ce53b305 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_ugly-7.js @@ -0,0 +1,5 @@ +// Copyright Test Inc. +// +// etc... +// etc... +function foo(){}; foo();
\ No newline at end of file diff --git a/devtools/client/debugger/test/mochitest/code_ugly-8 b/devtools/client/debugger/test/mochitest/code_ugly-8 new file mode 100644 index 000000000..dc0d18500 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_ugly-8 @@ -0,0 +1,3 @@ +function foo() { var a=1; var b=2; bar(a, b); } +function bar(c, d) { debugger; } +foo(); diff --git a/devtools/client/debugger/test/mochitest/code_ugly-8^headers^ b/devtools/client/debugger/test/mochitest/code_ugly-8^headers^ new file mode 100644 index 000000000..a17a9a3a1 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_ugly-8^headers^ @@ -0,0 +1 @@ +Content-Type: application/javascript diff --git a/devtools/client/debugger/test/mochitest/code_ugly.js b/devtools/client/debugger/test/mochitest/code_ugly.js new file mode 100644 index 000000000..dc0d18500 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_ugly.js @@ -0,0 +1,3 @@ +function foo() { var a=1; var b=2; bar(a, b); } +function bar(c, d) { debugger; } +foo(); diff --git a/devtools/client/debugger/test/mochitest/code_worker-source-map.coffee b/devtools/client/debugger/test/mochitest/code_worker-source-map.coffee new file mode 100644 index 000000000..446e3aefe --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_worker-source-map.coffee @@ -0,0 +1,22 @@ +# Uses a binary search algorithm to locate a value in the specified array. +binary_search = (items, value) -> + + start = 0 + stop = items.length - 1 + pivot = Math.floor (start + stop) / 2 + + while items[pivot] isnt value and start < stop + + # Adjust the search area. + stop = pivot - 1 if value < items[pivot] + start = pivot + 1 if value > items[pivot] + + # Recalculate the pivot. + pivot = Math.floor (stop + start) / 2 + + # Make sure we've found the correct value. + if items[pivot] is value then pivot else -1 + +self.onmessage = (event) -> + data = event.data + binary_search(data.items, data.value) diff --git a/devtools/client/debugger/test/mochitest/code_worker-source-map.js b/devtools/client/debugger/test/mochitest/code_worker-source-map.js new file mode 100644 index 000000000..9a9f541a9 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_worker-source-map.js @@ -0,0 +1,35 @@ +// Generated by CoffeeScript 1.10.0 +(function() { + var binary_search; + + binary_search = function(items, value) { + var pivot, start, stop; + start = 0; + stop = items.length - 1; + pivot = Math.floor((start + stop) / 2); + while (items[pivot] !== value && start < stop) { + if (value < items[pivot]) { + stop = pivot - 1; + } + if (value > items[pivot]) { + start = pivot + 1; + } + pivot = Math.floor((stop + start) / 2); + } + if (items[pivot] === value) { + return pivot; + } else { + return -1; + } + }; + + self.onmessage = function(event) { + console.log("EUTA"); + var data; + data = event.data; + return binary_search(data.items, data.value); + }; + +}).call(this); + +//# sourceMappingURL=code_worker-source-map.js.map diff --git a/devtools/client/debugger/test/mochitest/code_worker-source-map.js.map b/devtools/client/debugger/test/mochitest/code_worker-source-map.js.map new file mode 100644 index 000000000..97c801a58 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_worker-source-map.js.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "file": "code_worker-source-map.js", + "sourceRoot": "", + "sources": [ + "code_worker-source-map.coffee" + ], + "names": [], + "mappings": ";AACA;AAAA,MAAA;;EAAA,aAAA,GAAgB,SAAC,KAAD,EAAQ,KAAR;AAEd,QAAA;IAAA,KAAA,GAAQ;IACR,IAAA,GAAQ,KAAK,CAAC,MAAN,GAAe;IACvB,KAAA,GAAQ,IAAI,CAAC,KAAL,CAAW,CAAC,KAAA,GAAQ,IAAT,CAAA,GAAiB,CAA5B;AAER,WAAM,KAAM,CAAA,KAAA,CAAN,KAAkB,KAAlB,IAA4B,KAAA,GAAQ,IAA1C;MAGE,IAAqB,KAAA,GAAQ,KAAM,CAAA,KAAA,CAAnC;QAAA,IAAA,GAAQ,KAAA,GAAQ,EAAhB;;MACA,IAAqB,KAAA,GAAQ,KAAM,CAAA,KAAA,CAAnC;QAAA,KAAA,GAAQ,KAAA,GAAQ,EAAhB;;MAGA,KAAA,GAAQ,IAAI,CAAC,KAAL,CAAW,CAAC,IAAA,GAAO,KAAR,CAAA,GAAiB,CAA5B;IAPV;IAUA,IAAG,KAAM,CAAA,KAAA,CAAN,KAAgB,KAAnB;aAA8B,MAA9B;KAAA,MAAA;aAAyC,CAAC,EAA1C;;EAhBc;;EAkBhB,IAAI,CAAC,SAAL,GAAiB,SAAC,KAAD;AACf,QAAA;IAAA,IAAA,GAAO,KAAK,CAAC;WACb,aAAA,CAAc,IAAI,CAAC,KAAnB,EAA0B,IAAI,CAAC,KAA/B;EAFe;AAlBjB" +} diff --git a/devtools/client/debugger/test/mochitest/code_workeractor-worker.js b/devtools/client/debugger/test/mochitest/code_workeractor-worker.js new file mode 100644 index 000000000..18f14864d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/code_workeractor-worker.js @@ -0,0 +1,5 @@ +"use strict"; + +self.onmessage = function () {}; + +postMessage("load"); diff --git a/devtools/client/debugger/test/mochitest/doc_WorkerActor.attach-tab1.html b/devtools/client/debugger/test/mochitest/doc_WorkerActor.attach-tab1.html new file mode 100644 index 000000000..62ab9be7d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_WorkerActor.attach-tab1.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"/> + </head> + <body> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_WorkerActor.attach-tab2.html b/devtools/client/debugger/test/mochitest/doc_WorkerActor.attach-tab2.html new file mode 100644 index 000000000..62ab9be7d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_WorkerActor.attach-tab2.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"/> + </head> + <body> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_WorkerActor.attachThread-tab.html b/devtools/client/debugger/test/mochitest/doc_WorkerActor.attachThread-tab.html new file mode 100644 index 000000000..62ab9be7d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_WorkerActor.attachThread-tab.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"/> + </head> + <body> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_auto-pretty-print-01.html b/devtools/client/debugger/test/mochitest/doc_auto-pretty-print-01.html new file mode 100644 index 000000000..dee2d52f2 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_auto-pretty-print-01.html @@ -0,0 +1,14 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> + +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <title>Auto Pretty Printing Test Page</title> + </head> + <body> + <script src="code_ugly-5.js"></script> + <script src="code_ugly-6.js"></script> + </body> +</html>
\ No newline at end of file diff --git a/devtools/client/debugger/test/mochitest/doc_auto-pretty-print-02.html b/devtools/client/debugger/test/mochitest/doc_auto-pretty-print-02.html new file mode 100644 index 000000000..e96a63d9e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_auto-pretty-print-02.html @@ -0,0 +1,14 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> + +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <title>Auto Pretty Printing Test Page</title> + </head> + <body> + <script src="code_ugly-6.js"></script> + <script src="code_ugly-7.js"></script> + </body> +</html>
\ No newline at end of file diff --git a/devtools/client/debugger/test/mochitest/doc_binary_search.html b/devtools/client/debugger/test/mochitest/doc_binary_search.html new file mode 100644 index 000000000..803106fc5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_binary_search.html @@ -0,0 +1,15 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script type="text/javascript" src="code_binary_search.js"></script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_blackboxing.html b/devtools/client/debugger/test/mochitest/doc_blackboxing.html new file mode 100644 index 000000000..a83b16de5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_blackboxing.html @@ -0,0 +1,26 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script type="text/javascript" src="code_blackboxing_blackboxme.js"></script> + <script type="text/javascript" src="code_blackboxing_one.js"></script> + <script type="text/javascript" src="code_blackboxing_two.js"></script> + <script type="text/javascript" src="code_blackboxing_three.js"></script> + <script> + function runTest() { + blackboxme(doDebuggerStatement); + } + function doDebuggerStatement() { + debugger; + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_blackboxing_unblackbox.html b/devtools/client/debugger/test/mochitest/doc_blackboxing_unblackbox.html new file mode 100644 index 000000000..3b26b25e3 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_blackboxing_unblackbox.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Debugger test page</title> + <script type="text/javascript" src="code_blackboxing_unblackbox.min.js"></script> +</head> +<body> + +</body> +</html>
\ No newline at end of file diff --git a/devtools/client/debugger/test/mochitest/doc_breakpoint-move.html b/devtools/client/debugger/test/mochitest/doc_breakpoint-move.html new file mode 100644 index 000000000..5124bbbcf --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_breakpoint-move.html @@ -0,0 +1,25 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button onclick="ermahgerd()">Click me!</button> + + <script type="text/javascript"> + function ermahgerd() { + debugger; + // This is just a line + // and here we are + var x = 5; + return x; + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_breakpoints-break-on-last-line-of-script-on-reload.html b/devtools/client/debugger/test/mochitest/doc_breakpoints-break-on-last-line-of-script-on-reload.html new file mode 100644 index 000000000..c1730e506 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_breakpoints-break-on-last-line-of-script-on-reload.html @@ -0,0 +1,8 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> +<head> + <meta charset="utf-8"/> + <title>Debugger Break on Last Line of Script on Reload Test Page</title> +</head> +<script src="code_breakpoints-break-on-last-line-of-script-on-reload.js"></script> diff --git a/devtools/client/debugger/test/mochitest/doc_breakpoints-other-tabs.html b/devtools/client/debugger/test/mochitest/doc_breakpoints-other-tabs.html new file mode 100644 index 000000000..4273dbdd8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_breakpoints-other-tabs.html @@ -0,0 +1,8 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> +<head> + <meta charset="utf-8"/> + <title>Debugger Breakpoints Other Tabs Test Page</title> +</head> +<script src="code_breakpoints-other-tabs.js"></script> diff --git a/devtools/client/debugger/test/mochitest/doc_breakpoints-reload.html b/devtools/client/debugger/test/mochitest/doc_breakpoints-reload.html new file mode 100644 index 000000000..0c6059c6d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_breakpoints-reload.html @@ -0,0 +1,13 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> +<head> + <meta charset="utf-8"/> + <title>Debugger Breakpoints Other Tabs Test Page</title> +</head> +<script> + function theTest() { + window.foo = "break on me"; + } + theTest(); +</script> diff --git a/devtools/client/debugger/test/mochitest/doc_bug-896139.html b/devtools/client/debugger/test/mochitest/doc_bug-896139.html new file mode 100644 index 000000000..166ad604f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_bug-896139.html @@ -0,0 +1,18 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> + +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"/> + <script> + window.onload = function () { + var script = document.createElement("script"); + script.setAttribute("src", "code_bug-896139.js"); + document.body.appendChild(script); + } + </script> + </head> + <body> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_closure-optimized-out.html b/devtools/client/debugger/test/mochitest/doc_closure-optimized-out.html new file mode 100644 index 000000000..3ad4e8fc0 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_closure-optimized-out.html @@ -0,0 +1,34 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset='utf-8'/> + <title>Debugger Test for Inspecting Optimized-Out Variables</title> + <!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> + <script type="text/javascript"> + window.addEventListener("load", function onload() { + window.removeEventListener("load", onload); + function clickHandler(event) { + button.removeEventListener("click", clickHandler, false); + function outer(arg) { + var upvar = arg * 2; + // The inner lambda only aliases arg, so the frontend alias analysis decides + // that upvar is not aliased and is not in the CallObject. + return function () { + arg += 2; + }; + } + + var f = outer(42); + f(); + } + var button = document.querySelector("button"); + button.addEventListener("click", clickHandler, false); + }); + </script> + + </head> + <body> + <button>Click me!</button> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_closures.html b/devtools/client/debugger/test/mochitest/doc_closures.html new file mode 100644 index 000000000..1ba91601a --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_closures.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> + <head> + <meta charset='utf-8'/> + <title>Debugger Test for Closure Inspection</title> + <!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> + <script type="text/javascript"> + window.addEventListener("load", function onload() { + window.removeEventListener("load", onload); + function clickHandler(event) { + button.removeEventListener("click", clickHandler, false); + var PersonFactory = function _pfactory(name) { + var foo = 10; + return { + getName: function() { return name; }, + getFoo: function() { foo = Date.now(); return foo; } + }; + }; + var person = new PersonFactory("Bob"); + debugger; + } + var button = document.querySelector("button"); + button.addEventListener("click", clickHandler, false); + }); + </script> + + </head> + <body> + <button>Click me!</button> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_cmd-break.html b/devtools/client/debugger/test/mochitest/doc_cmd-break.html new file mode 100644 index 000000000..4f434746e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_cmd-break.html @@ -0,0 +1,22 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script type="text/javascript"> + function firstCall() { + window.gLineNumber = Error().lineNumber; secondCall(); + } + function secondCall() { + debugger; + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_cmd-dbg.html b/devtools/client/debugger/test/mochitest/doc_cmd-dbg.html new file mode 100644 index 000000000..5ab41eb1b --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_cmd-dbg.html @@ -0,0 +1,40 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <input type="text" value=""/> + <input type="button" value="Click me!" onclick="test()"/> + + <script type="application/javascript;version=1.7"> + let output = document.querySelector("input"); + output.value = ""; + + function test() { + debugger; + stepIntoMe(); // step in + + output.value = "dbg continue"; + debugger; + } + + function stepIntoMe() { + output.value = "step in"; // step in + stepOverMe(); // step over + let x = 0; // step out + output.value = "step out"; + } + + function stepOverMe() { + output.value = "step over"; + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_conditional-breakpoints.html b/devtools/client/debugger/test/mochitest/doc_conditional-breakpoints.html new file mode 100644 index 000000000..7adce7a18 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_conditional-breakpoints.html @@ -0,0 +1,35 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button onclick="ermahgerd()">Click me!</button> + + <script type="text/javascript"> + function ermahgerd() { + var a = {}; + debugger; + a = "undefined"; + a = "null"; + a = "42"; + a = "true"; + a = "'nasu'"; + a = "/regexp/"; + a = "{}"; + a = "function() {}"; + a = "(function { return false; })()"; + a = "a"; + a = "a !== undefined"; + a = "a !== null"; + a = "b"; + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_domnode-variables.html b/devtools/client/debugger/test/mochitest/doc_domnode-variables.html new file mode 100644 index 000000000..9e7531036 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_domnode-variables.html @@ -0,0 +1,24 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <div>Look at this DIV! Just look at it!</div> + + <script type="text/javascript"> + function start() { + var theDiv = document.querySelector("div"); + var theBody = document.body; + var manyDomNodes = [theDiv, theBody]; + debugger; + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_editor-mode.html b/devtools/client/debugger/test/mochitest/doc_editor-mode.html new file mode 100644 index 000000000..8e3573cea --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_editor-mode.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script type="text/javascript" src="code_script-switching-01.js?a=b"></script> + <script type="text/javascript" src="code_test-editor-mode?c=d"></script> + <script type="text/javascript"> + function banana() { + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_empty-tab-01.html b/devtools/client/debugger/test/mochitest/doc_empty-tab-01.html new file mode 100644 index 000000000..28398f776 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_empty-tab-01.html @@ -0,0 +1,14 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Empty test page 1</title> + </head> + + <body> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_empty-tab-02.html b/devtools/client/debugger/test/mochitest/doc_empty-tab-02.html new file mode 100644 index 000000000..5db150844 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_empty-tab-02.html @@ -0,0 +1,14 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Empty test page 2</title> + </head> + + <body> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_event-listeners-01.html b/devtools/client/debugger/test/mochitest/doc_event-listeners-01.html new file mode 100644 index 000000000..b44400311 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_event-listeners-01.html @@ -0,0 +1,43 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button>Click me!</button> + <input type="text" onchange="changeHandler()"> + + <script type="text/javascript"> + window.addEventListener("load", function onload() { + window.removeEventListener("load", onload); + function initialSetup(event) { + debugger; + var button = document.querySelector("button"); + button.onclick = clickHandler; + } + function clickHandler(event) { + window.foobar = "clickHandler"; + } + function changeHandler(event) { + window.foobar = "changeHandler"; + } + function keyupHandler(event) { + window.foobar = "keyupHandler"; + } + + var button = document.querySelector("button"); + button.onclick = initialSetup; + + var input = document.querySelector("input"); + input.addEventListener("keyup", keyupHandler, true); + + window.changeHandler = changeHandler; + }); + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_event-listeners-02.html b/devtools/client/debugger/test/mochitest/doc_event-listeners-02.html new file mode 100644 index 000000000..6a4649de9 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_event-listeners-02.html @@ -0,0 +1,53 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button>Click me!</button> + <input type="text" onchange="changeHandler()"> + + <script type="text/javascript"> + window.addEventListener("load", function onload() { + window.removeEventListener("load", onload); + function initialSetup(event) { + debugger; + var button = document.querySelector("button"); + button.onclick = clickHandler; + } + function clickHandler(event) { + window.foobar = "clickHandler"; + } + function changeHandler(event) { + window.foobar = "changeHandler"; + } + function keyupHandler(event) { + window.foobar = "keyupHandler"; + } + function keydownHandler(event) { + window.foobar = "keydownHandler"; + } + + var button = document.querySelector("button"); + button.onclick = initialSetup; + + var input = document.querySelector("input"); + input.addEventListener("keyup", keyupHandler, true); + + window.addEventListener("keydown", keydownHandler, true); + document.body.addEventListener("keydown", keydownHandler, true); + + window.changeHandler = changeHandler; + }); + + function addBodyClickEventListener() { + document.body.addEventListener("click", function() { debugger; }); + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_event-listeners-03.html b/devtools/client/debugger/test/mochitest/doc_event-listeners-03.html new file mode 100644 index 000000000..b672a4360 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_event-listeners-03.html @@ -0,0 +1,63 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> +<html> + <head> + <meta charset="utf-8"/> + <title>Bound event listeners test page</title> + </head> + + <body> + <button id="initialSetup">initialSetup</button> + <button id="clicker">clicker</button> + <button id="handleEventClick">handleEventClick</button> + <button id="boundHandleEventClick">boundHandleEventClick</button> + + <script type="text/javascript"> + window.addEventListener("load", function onload() { + window.removeEventListener("load", onload); + function initialSetup(event) { + var button = document.getElementById("initialSetup"); + button.removeEventListener("click", initialSetup); + debugger; + } + + function clicker(event) { + window.foobar = "clicker"; + } + + function handleEventClick() { + var button = document.getElementById("handleEventClick"); + // Create a long prototype chain to test for weird edge cases. + button.addEventListener("click", Object.create(Object.create(this))); + } + + handleEventClick.prototype.handleEvent = function() { + window.foobar = "handleEventClick"; + }; + + function boundHandleEventClick() { + var button = document.getElementById("boundHandleEventClick"); + this.handleEvent = this.handleEvent.bind(this); + button.addEventListener("click", this); + } + + boundHandleEventClick.prototype.handleEvent = function() { + window.foobar = "boundHandleEventClick"; + }; + + var button = document.getElementById("clicker"); + // Bind more than once to test for weird edge cases. + var boundClicker = clicker.bind(this).bind(this).bind(this); + button.addEventListener("click", boundClicker); + + new handleEventClick(); + new boundHandleEventClick(); + + var initButton = document.getElementById("initialSetup"); + initButton.addEventListener("click", initialSetup); + }); + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_event-listeners-04.html b/devtools/client/debugger/test/mochitest/doc_event-listeners-04.html new file mode 100644 index 000000000..d92488a70 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_event-listeners-04.html @@ -0,0 +1,23 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button>Click me!</button> + + <script type="text/javascript"> + window.addEventListener("load", function onload() { + var button = document.querySelector("button"); + button.onclick = function () { + debugger; + }; + }); + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_frame-parameters.html b/devtools/client/debugger/test/mochitest/doc_frame-parameters.html new file mode 100644 index 000000000..b3108d6bf --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_frame-parameters.html @@ -0,0 +1,37 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button onclick="start()">Click me!</button> + + <script type="text/javascript"> + function test(aArg, bArg, cArg, dArg, eArg, fArg) { + var a = 1; + var b = { a: a }; + var c = { a: 1, b: "beta", c: 3, d: false, e: null, f: fArg }; + var myVar = { + _prop: 42, + get prop() { return this._prop; }, + set prop(val) { this._prop = val; } + }; + debugger; + } + + function start() { + var a = { a: 1, b: "beta", c: 3, d: false, e: null, f: undefined }; + var e = eval("test(a, 'beta', 3, false, null);"); + } + + var button = document.querySelector("button"); + var buttonAsProto = Object.create(button); + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_function-display-name.html b/devtools/client/debugger/test/mochitest/doc_function-display-name.html new file mode 100644 index 000000000..84e8ce6e1 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_function-display-name.html @@ -0,0 +1,31 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script type="text/javascript"> + var a = function() { + return function() { + debugger; + } + } + + var anon = a(); + anon.displayName = "anonFunc"; + + var inferred = a(); + + function evalCall() { + eval("anon();"); + eval("inferred();"); + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_function-jump.html b/devtools/client/debugger/test/mochitest/doc_function-jump.html new file mode 100644 index 000000000..0cd99e662 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_function-jump.html @@ -0,0 +1,17 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <p>Foo bar, bar, bazz, bar foo bar!</p> + + <script type="text/javascript" src="code_function-jump-01.js"></script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_function-search.html b/devtools/client/debugger/test/mochitest/doc_function-search.html new file mode 100644 index 000000000..eb0e7eaea --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_function-search.html @@ -0,0 +1,30 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <p>Peanut butter jelly time!</p> + + <script type="text/javascript" src="code_function-search-01.js"></script> + <script type="text/javascript" src="code_function-search-02.js"></script> + <script type="text/javascript" src="code_function-search-03.js"></script> + + <script type="text/javascript;version=1.8"> + function inline() {} + var arrow = () => {} + + var foo = bar => {} + var foo2 = bar2 = baz2 => 42; + + setTimeout((foo, bar, baz) => {}); + setTimeout((foo, bar, baz) => 42); + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_global-method-override.html b/devtools/client/debugger/test/mochitest/doc_global-method-override.html new file mode 100644 index 000000000..d8cf750fc --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_global-method-override.html @@ -0,0 +1,16 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"> + <title>Debugger global method override test page</title> + </head> + <body> + <script type="text/javascript"> + console.log( "Error: " + toString( { x: 0, y: 0 } ) ); + function toString(v) { return "[ " + v.x + ", " + v.y + " ]"; } + </script> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_iframes.html b/devtools/client/debugger/test/mochitest/doc_iframes.html new file mode 100644 index 000000000..e5a76c280 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_iframes.html @@ -0,0 +1,15 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <iframe src="doc_inline-debugger-statement.html"></iframe> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_included-script.html b/devtools/client/debugger/test/mochitest/doc_included-script.html new file mode 100644 index 000000000..8b134dd42 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_included-script.html @@ -0,0 +1,22 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button onclick="myFunction()">Click me!</button> + + <script type="text/javascript" src="code_location-changes.js"></script> + <script type="text/javascript"> + function runDebuggerStatement() { + debugger; + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_inline-debugger-statement.html b/devtools/client/debugger/test/mochitest/doc_inline-debugger-statement.html new file mode 100644 index 000000000..406e9d9da --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_inline-debugger-statement.html @@ -0,0 +1,21 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button>Click me!</button> + + <script type="text/javascript"> + function runDebuggerStatement() { + debugger; + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_inline-script.html b/devtools/client/debugger/test/mochitest/doc_inline-script.html new file mode 100644 index 000000000..d071cc084 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_inline-script.html @@ -0,0 +1,25 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button onclick="myFunction()">Click me!</button> + + <script type="text/javascript"> + function runDebuggerStatement() { + debugger; + } + function myFunction() { + var a = 1; + debugger; + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_large-array-buffer.html b/devtools/client/debugger/test/mochitest/doc_large-array-buffer.html new file mode 100644 index 000000000..25b1b4d4e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_large-array-buffer.html @@ -0,0 +1,32 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button onclick="test(10000)">Click me!</button> + + <script type="text/javascript"> + function test(aNumber) { + var buffer = new ArrayBuffer(aNumber); + var largeArray = new Int8Array(buffer); + var largeObject = {}; + var largeMap = new Map(); + var largeSet = new Set(); + + for (var i = 0; i < aNumber; i++) { + let value = aNumber - i - 1; + largeObject[i] = value; + largeMap.set(i, value); + largeSet.add(value); + } + debugger; + } + </script> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_listworkers-tab.html b/devtools/client/debugger/test/mochitest/doc_listworkers-tab.html new file mode 100644 index 000000000..62ab9be7d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_listworkers-tab.html @@ -0,0 +1,8 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"/> + </head> + <body> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_map-set.html b/devtools/client/debugger/test/mochitest/doc_map-set.html new file mode 100644 index 000000000..7c2c624e1 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_map-set.html @@ -0,0 +1,42 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page for Maps and Sets</title> + </head> + + <body> + <script> + function startTest() { + let obj0 = { a: 0 }; + let obj1 = { a: 1 }; + + let map = new Map(); + map.set(obj0, 0); + map.set(obj1, 1); + map.extraProp = true; + + let weakMap = new WeakMap(); + weakMap.set(obj0, 0); + weakMap.set(obj1, 1); + weakMap.extraProp = true; + + let set = new Set(); + set.add(obj0); + set.add(obj1); + set.extraProp = true; + + let weakSet = new WeakSet(); + weakSet.add(obj0); + weakSet.add(obj1); + weakSet.extraProp = true; + + debugger; + }; + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_minified.html b/devtools/client/debugger/test/mochitest/doc_minified.html new file mode 100644 index 000000000..b229e079f --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_minified.html @@ -0,0 +1,14 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script src="code_math.min.js"></script> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_minified_bogus_map.html b/devtools/client/debugger/test/mochitest/doc_minified_bogus_map.html new file mode 100644 index 000000000..d6670a7e1 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_minified_bogus_map.html @@ -0,0 +1,14 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script src="code_math_bogus_map.js"></script> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_native-event-handler.html b/devtools/client/debugger/test/mochitest/doc_native-event-handler.html new file mode 100644 index 000000000..cd2a656bf --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_native-event-handler.html @@ -0,0 +1,22 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <title>A video element with native event handlers</title> + <script type="text/javascript"> + function initialSetup(event) { + debugger; + } + + window.addEventListener("load", function() {}, false); + </script> + </head> + <body> + <button onclick="initialSetup()">Click me!</button> + <!-- the "controls" attribute ensures that there are extra event handlers in + the element. --> + <video controls></video> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_no-page-sources.html b/devtools/client/debugger/test/mochitest/doc_no-page-sources.html new file mode 100644 index 000000000..5131578ad --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_no-page-sources.html @@ -0,0 +1,11 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> +<html> + <head> + <meta charset="utf-8"/> + <title>This page has no sources</title> + </head> + <body> + </body> +</html>
\ No newline at end of file diff --git a/devtools/client/debugger/test/mochitest/doc_pause-exceptions.html b/devtools/client/debugger/test/mochitest/doc_pause-exceptions.html new file mode 100644 index 000000000..7766fb49d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_pause-exceptions.html @@ -0,0 +1,35 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button>Click me!</button> + <ul></ul> + + <script type="text/javascript"> + window.addEventListener("load", function() { + function test() { + try { + throw new Error("boom"); + } catch (e) { + var list = document.querySelector("ul"); + var item = document.createElement("li"); + item.innerHTML = e.message; + list.appendChild(item); + } finally { + debugger; + } + } + var button = document.querySelector("button"); + button.addEventListener("click", test, false); + }); + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_pretty-print-2.html b/devtools/client/debugger/test/mochitest/doc_pretty-print-2.html new file mode 100644 index 000000000..509f57d6b --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_pretty-print-2.html @@ -0,0 +1,15 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> +<head> + <meta charset="utf-8"/> + <title>Debugger Pretty Printing Test Page</title> +</head> +<script src="code_ugly-2.js"></script> +<script src="code_ugly-3.js"></script> +<script src="code_ugly-4.js"></script> +<script> + function noop(x) { + return x; + } +</script> diff --git a/devtools/client/debugger/test/mochitest/doc_pretty-print-3.html b/devtools/client/debugger/test/mochitest/doc_pretty-print-3.html new file mode 100644 index 000000000..6192642f3 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_pretty-print-3.html @@ -0,0 +1,8 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> +<head> + <meta charset="utf-8"/> + <title>Debugger Pretty Printing Test Page</title> +</head> +<script src="code_ugly-8"></script> diff --git a/devtools/client/debugger/test/mochitest/doc_pretty-print-on-paused.html b/devtools/client/debugger/test/mochitest/doc_pretty-print-on-paused.html new file mode 100644 index 000000000..a431d0898 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_pretty-print-on-paused.html @@ -0,0 +1,14 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> + +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8" /> + <title>Pretty printing when debugger is paused Test Page</title> + </head> + <body> + <script src="code_ugly-2.js"></script> + <script src="code_script-switching-02.js"></script> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_pretty-print.html b/devtools/client/debugger/test/mochitest/doc_pretty-print.html new file mode 100644 index 000000000..dcf595a8d --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_pretty-print.html @@ -0,0 +1,8 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> +<head> + <meta charset="utf-8"/> + <title>Debugger Pretty Printing Test Page</title> +</head> +<script src="code_ugly.js"></script> diff --git a/devtools/client/debugger/test/mochitest/doc_promise-get-allocation-stack.html b/devtools/client/debugger/test/mochitest/doc_promise-get-allocation-stack.html new file mode 100644 index 000000000..48546c967 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_promise-get-allocation-stack.html @@ -0,0 +1,24 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Promise test page</title> + </head> + + <body> + <script type="text/javascript"> + function makePromises() { + var p = new Promise(() => {}); + p.name = "p"; + var q = p.then(); + q.name = "q"; + var r = p.then(null, () => {}); + r.name = "r"; + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_promise-get-fulfillment-stack.html b/devtools/client/debugger/test/mochitest/doc_promise-get-fulfillment-stack.html new file mode 100644 index 000000000..0b311a69a --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_promise-get-fulfillment-stack.html @@ -0,0 +1,24 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Promise test page</title> + </head> + + <body> + <script type="text/javascript"> + function makePromise() { + var p = returnPromise(); + p.name = "p"; + } + + function returnPromise() { + return new Promise(resolve => resolve("hello")); + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_promise-get-rejection-stack.html b/devtools/client/debugger/test/mochitest/doc_promise-get-rejection-stack.html new file mode 100644 index 000000000..9fe203595 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_promise-get-rejection-stack.html @@ -0,0 +1,24 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Promise test page</title> + </head> + + <body> + <script type="text/javascript"> + function makePromise() { + var p = returnPromise(); + p.name = "p"; + } + + function returnPromise() { + return new Promise((resolve, reject) => reject("hello")); + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_promise.html b/devtools/client/debugger/test/mochitest/doc_promise.html new file mode 100644 index 000000000..fe6c1d807 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_promise.html @@ -0,0 +1,30 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger + Promise test page</title> + </head> + + <body> + <script> + window.pending = new Promise(function () {}); + window.fulfilled = Promise.resolve({ a: 1, b: 2, c: 3 }); + window.rejected = Promise.reject(new Error("uh oh")); + + window.doPause = function () { + var p = window.pending; + var f = window.fulfilled; + var r = window.rejected; + debugger; + }; + + // Attach an error handler so that the logs don't have a warning about an + // unhandled, rejected promise. + window.rejected.then(null, function () {}); + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_proxy.html b/devtools/client/debugger/test/mochitest/doc_proxy.html new file mode 100644 index 000000000..e2f35104a --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_proxy.html @@ -0,0 +1,39 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger + Proxy test page</title> + </head> + + <body> + <script> + window.target = {name: "target"}; + window.handler = { /* Debugging a proxy shouldn't run any trap */ + name: "handler", + getPrototypeOf() { throw new Error("proxy getPrototypeOf trap was called"); }, + setPrototypeOf() { throw new Error("proxy setPrototypeOf trap was called"); }, + isExtensible() { throw new Error("proxy isExtensible trap was called"); }, + preventExtensions() { throw new Error("proxy preventExtensions trap was called"); }, + getOwnPropertyDescriptor() { throw new Error("proxy getOwnPropertyDescriptor trap was called"); }, + defineProperty() { throw new Error("proxy defineProperty trap was called"); }, + has() { throw new Error("proxy has trap was called"); }, + get() { throw new Error("proxy get trap was called"); }, + set() { throw new Error("proxy set trap was called"); }, + deleteProperty() { throw new Error("proxy deleteProperty trap was called"); }, + ownKeys() { throw new Error("proxy ownKeys trap was called"); }, + apply() { throw new Error("proxy apply trap was called"); }, + construct() { throw new Error("proxy construct trap was called"); } + }; + window.proxy = new Proxy(target, handler); + + window.doPause = function () { + var proxy = window.proxy; + debugger; + }; + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_random-javascript.html b/devtools/client/debugger/test/mochitest/doc_random-javascript.html new file mode 100644 index 000000000..69269e409 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_random-javascript.html @@ -0,0 +1,15 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script src="sjs_random-javascript.sjs"></script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_recursion-stack.html b/devtools/client/debugger/test/mochitest/doc_recursion-stack.html new file mode 100644 index 000000000..d68fb1d18 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_recursion-stack.html @@ -0,0 +1,35 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script type="text/javascript"> + function simpleCall() { + debugger; + } + + function evalCall() { + eval("debugger;"); + } + + var gRecurseLimit = 100; + var gRecurseDepth = 0; + + function recurse() { + if (++gRecurseDepth == gRecurseLimit) { + debugger; + gRecurseDepth = 0; + return; + } + recurse(); + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_scope-variable-2.html b/devtools/client/debugger/test/mochitest/doc_scope-variable-2.html new file mode 100644 index 000000000..afbfd166a --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_scope-variable-2.html @@ -0,0 +1,30 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script type="text/javascript"> + function test() { + var a = "first scope"; + firstNest(); + + function firstNest() { + var a = "second scope"; + secondNest(); + + function secondNest() { + var a = "third scope"; + debugger; + } + } + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_scope-variable-3.html b/devtools/client/debugger/test/mochitest/doc_scope-variable-3.html new file mode 100644 index 000000000..fcd45cc0a --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_scope-variable-3.html @@ -0,0 +1,23 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script type="text/javascript"> + var trap = "first script"; + function test() { + debugger; + } + </script> + <script type="text/javascript">/* + trololol + */</script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_scope-variable-4.html b/devtools/client/debugger/test/mochitest/doc_scope-variable-4.html new file mode 100644 index 000000000..17b0e3b10 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_scope-variable-4.html @@ -0,0 +1,25 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script type="text/javascript"> + function test() { + var a = "first scope"; + nest(); + + function nest() { + var a = "second scope"; + debugger; + } + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_scope-variable.html b/devtools/client/debugger/test/mochitest/doc_scope-variable.html new file mode 100644 index 000000000..3fa28fab9 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_scope-variable.html @@ -0,0 +1,25 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script type="text/javascript"> + function test() { + var a = "first scope"; + nest(); + } + + function nest() { + var a = "second scope"; + debugger; + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_script-bookmarklet.html b/devtools/client/debugger/test/mochitest/doc_script-bookmarklet.html new file mode 100644 index 000000000..922010062 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_script-bookmarklet.html @@ -0,0 +1,14 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script>function injectBookmarklet(bookmarklet) { setTimeout(function() { window.location = "javascript:" + bookmarklet; }, 0); }</script> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_script-eval.html b/devtools/client/debugger/test/mochitest/doc_script-eval.html new file mode 100644 index 000000000..7e3f253bb --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_script-eval.html @@ -0,0 +1,16 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button onclick="evalSource()">Click me!</button> + + <script type="text/javascript" src="code_script-eval.js"></script> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_script-switching-01.html b/devtools/client/debugger/test/mochitest/doc_script-switching-01.html new file mode 100644 index 000000000..afb4484b5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_script-switching-01.html @@ -0,0 +1,18 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button onclick="firstCall()">Click me!</button> + + <script type="text/javascript" src="code_script-switching-01.js"></script> + <script type="text/javascript" src="code_script-switching-02.js"></script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_script-switching-02.html b/devtools/client/debugger/test/mochitest/doc_script-switching-02.html new file mode 100644 index 000000000..cceeea2c8 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_script-switching-02.html @@ -0,0 +1,18 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button onclick="firstCall()">Click me!</button> + + <script type="text/javascript" src="code_script-switching-01.js"></script> + <script type="text/javascript" src="code_script-switching-02.js?foo=bar,baz|lol"></script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_script_webext_contentscript.html b/devtools/client/debugger/test/mochitest/doc_script_webext_contentscript.html new file mode 100644 index 000000000..8e88997db --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_script_webext_contentscript.html @@ -0,0 +1,13 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_split-console-paused-reload.html b/devtools/client/debugger/test/mochitest/doc_split-console-paused-reload.html new file mode 100644 index 000000000..113c53468 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_split-console-paused-reload.html @@ -0,0 +1,22 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Test page for opening a split-console when execution is paused</title> + </head> + + <body> + <script type="text/javascript"> + function executeFunction() { + let privateVar = { propKey: "privateVarValue" }; + + window.foobar = "foobar"; + } + executeFunction(); + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_step-many-statements.html b/devtools/client/debugger/test/mochitest/doc_step-many-statements.html new file mode 100644 index 000000000..edd0e2882 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_step-many-statements.html @@ -0,0 +1,50 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button id="start">Start!</button> + + <script type="text/javascript"> + function normal(aArg) { + debugger; + var r = 10; + var a = squareAndOne(r); + var b = squareUntil(r, 99999999999); //recurses 3 times, returns on 4th call + var c = addUntil(r, 5, 1050); // recurses 208 times and returns on the 209th call + return a + b + c; + + } + + function squareAndOne(arg){ + return (arg * arg) + 1; + } + function squareUntil(arg, limit){ + if(arg * arg >= limit){ + return arg * arg; + }else{ + return squareUntil(arg * arg, limit); + } + } + + function addUntil(arg1, arg2, limit){ + if(arg1 + arg2 > limit){ + return arg1 + arg2; + }else{ + return addUntil(arg1 + arg2, arg2, limit); + } + } + + var normalBtn = document.getElementById("start"); + normalBtn.addEventListener("click", normal, false); + + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_step-out.html b/devtools/client/debugger/test/mochitest/doc_step-out.html new file mode 100644 index 000000000..89eda2be1 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_step-out.html @@ -0,0 +1,42 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button id="return">Return me!</button> + <button id="throw">Throw me!</button> + + <script type="text/javascript"> + function normal(aArg) { + debugger; + var r = 10; + return r; + } + + function error(aArg) { + function inner(aArg) { + debugger; + var r = 10; + throw "boom"; + return r; + } + try { + inner(aArg); + } catch (e) {} + } + + var normalBtn = document.getElementById("return"); + normalBtn.addEventListener("click", normal, false); + + var throwBtn = document.getElementById("throw"); + throwBtn.addEventListener("click", error, false); + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_terminate-on-tab-close.html b/devtools/client/debugger/test/mochitest/doc_terminate-on-tab-close.html new file mode 100644 index 000000000..2101b3103 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_terminate-on-tab-close.html @@ -0,0 +1,20 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script type="text/javascript"> + function debuggerThenThrow() { + debugger; + throw "unreachable"; + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_watch-expression-button.html b/devtools/client/debugger/test/mochitest/doc_watch-expression-button.html new file mode 100644 index 000000000..a4a5be26e --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_watch-expression-button.html @@ -0,0 +1,31 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button onclick="start()">Click me!</button> + + <script type="text/javascript"> + function test() { + var a = 1; + var b = { a: a }; + b.a = 2; + debugger; + } + + function start() { + var e = eval('test();'); + } + + var button = document.querySelector("button"); + var buttonAsProto = Object.create(button); + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_watch-expressions.html b/devtools/client/debugger/test/mochitest/doc_watch-expressions.html new file mode 100644 index 000000000..487b5a5a5 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_watch-expressions.html @@ -0,0 +1,29 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <script type="text/javascript"> + function test() { + ermahgerd.call({ canada: new String("eh") }); + } + function ermahgerd(aArg) { + var t = document.title; + debugger; + (function() { + var a = undefined; + debugger; + var a = {}; + debugger; + }("sensational")); + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_whitespace-property-names.html b/devtools/client/debugger/test/mochitest/doc_whitespace-property-names.html new file mode 100644 index 000000000..6479a5978 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_whitespace-property-names.html @@ -0,0 +1,29 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!DOCTYPE html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger + Whitespace property name test page</title> + </head> + + <body> + <script> + window.doPause = function () { + var obj = { + "": 0, + " ": 1, + "\r": 2, + "\n": 3, + "\t": 4, + "\f": 5, + "\uFEFF": 6, + "\xA0": 7 + }; + debugger; + }; + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_with-frame.html b/devtools/client/debugger/test/mochitest/doc_with-frame.html new file mode 100644 index 000000000..8fa202b18 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_with-frame.html @@ -0,0 +1,29 @@ +<!-- Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ --> +<!doctype html> + +<html> + <head> + <meta charset="utf-8"/> + <title>Debugger test page</title> + </head> + + <body> + <button onclick="test(10)">Click me!</button> + + <script type="text/javascript"> + function test(aNumber) { + var a, obj = { alpha: 1, beta: 2 }; + var r = aNumber; + with (Math) { + a = PI * r * r; + with (obj) { + var foo = beta * PI; + debugger; + } + } + } + </script> + </body> + +</html> diff --git a/devtools/client/debugger/test/mochitest/doc_worker-source-map.html b/devtools/client/debugger/test/mochitest/doc_worker-source-map.html new file mode 100644 index 000000000..20a14e351 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/doc_worker-source-map.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"/> + <script> + var worker = new Worker("code_worker-source-map.js"); + + function binary_search(items, value) { + worker.postMessage({ + items: items, + value: value + }); + } + </script> + </head> + <body> + </body> +</html> diff --git a/devtools/client/debugger/test/mochitest/head.js b/devtools/client/debugger/test/mochitest/head.js new file mode 100644 index 000000000..1f9d38b82 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/head.js @@ -0,0 +1,1351 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// shared-head.js handles imports, constants, and utility functions +Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this); + +// Disable logging for faster test runs. Set this pref to true if you want to +// debug a test in your try runs. Both the debugger server and frontend will +// be affected by this pref. +var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log"); +Services.prefs.setBoolPref("devtools.debugger.log", false); + +var { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {}); +var { DebuggerServer } = require("devtools/server/main"); +var { DebuggerClient, ObjectClient } = require("devtools/shared/client/main"); +var { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {}); +var EventEmitter = require("devtools/shared/event-emitter"); +var { Toolbox } = require("devtools/client/framework/toolbox"); + +const chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry); + +// Override promise with deprecated-sync-thenables +promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise; + +const EXAMPLE_URL = "http://example.com/browser/devtools/client/debugger/test/mochitest/"; +const FRAME_SCRIPT_URL = getRootDirectory(gTestPath) + "code_frame-script.js"; +const CHROME_URL = "chrome://mochitests/content/browser/devtools/client/debugger/test/mochitest/"; +const CHROME_URI = Services.io.newURI(CHROME_URL, null, null); + +Services.prefs.setBoolPref("devtools.debugger.new-debugger-frontend", false); + +registerCleanupFunction(function* () { + Services.prefs.clearUserPref("devtools.debugger.new-debugger-frontend"); + + info("finish() was called, cleaning up..."); + Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging); + + while (gBrowser && gBrowser.tabs && gBrowser.tabs.length > 1) { + info("Destroying toolbox."); + let target = TargetFactory.forTab(gBrowser.selectedTab); + yield gDevTools.closeToolbox(target); + + info("Removing tab."); + gBrowser.removeCurrentTab(); + } + + // Properly shut down the server to avoid memory leaks. + DebuggerServer.destroy(); + + // Debugger tests use a lot of memory, so force a GC to help fragmentation. + info("Forcing GC after debugger test."); + Cu.forceGC(); +}); + +// Import the GCLI test helper +var testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); +testDir = testDir.replace(/\/\//g, "/"); +testDir = testDir.replace("chrome:/mochitest", "chrome://mochitest"); +var helpersjs = testDir + "/../../../commandline/test/helpers.js"; +Services.scriptloader.loadSubScript(helpersjs, this); + +function addWindow(aUrl) { + info("Adding window: " + aUrl); + return promise.resolve(getChromeWindow(window.open(aUrl))); +} + +function getChromeWindow(aWindow) { + return aWindow + .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem + .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); +} + +// Override addTab/removeTab as defined by shared-head, since these have +// an extra window parameter and add a frame script +this.addTab = function addTab(aUrl, aWindow) { + info("Adding tab: " + aUrl); + + let deferred = promise.defer(); + let targetWindow = aWindow || window; + let targetBrowser = targetWindow.gBrowser; + + targetWindow.focus(); + let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl); + let linkedBrowser = tab.linkedBrowser; + + info("Loading frame script with url " + FRAME_SCRIPT_URL + "."); + linkedBrowser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false); + + BrowserTestUtils.browserLoaded(linkedBrowser) + .then(function () { + info("Tab added and finished loading: " + aUrl); + deferred.resolve(tab); + }); + + return deferred.promise; +}; + +this.removeTab = function removeTab(aTab, aWindow) { + info("Removing tab."); + + let deferred = promise.defer(); + let targetWindow = aWindow || window; + let targetBrowser = targetWindow.gBrowser; + let tabContainer = targetBrowser.tabContainer; + + tabContainer.addEventListener("TabClose", function onClose(aEvent) { + tabContainer.removeEventListener("TabClose", onClose, false); + + info("Tab removed and finished closing."); + deferred.resolve(); + }, false); + + targetBrowser.removeTab(aTab); + return deferred.promise; +}; + +function getAddonURIFromPath(aPath) { + let chromeURI = Services.io.newURI(aPath, null, CHROME_URI); + return chromeRegistry.convertChromeURL(chromeURI).QueryInterface(Ci.nsIFileURL); +} + +function getTemporaryAddonURLFromPath(aPath) { + return getAddonURIFromPath(aPath).spec; +} + +function addTemporaryAddon(aPath) { + let addonFile = getAddonURIFromPath(aPath).file; + info("Installing addon: " + addonFile.path); + + return AddonManager.installTemporaryAddon(addonFile); +} + +function removeAddon(aAddon) { + info("Removing addon."); + + let deferred = promise.defer(); + + let listener = { + onUninstalled: function (aUninstalledAddon) { + if (aUninstalledAddon != aAddon) { + return; + } + AddonManager.removeAddonListener(listener); + deferred.resolve(); + } + }; + AddonManager.addAddonListener(listener); + aAddon.uninstall(); + + return deferred.promise; +} + +function getTabActorForUrl(aClient, aUrl) { + let deferred = promise.defer(); + + aClient.listTabs(aResponse => { + let tabActor = aResponse.tabs.filter(aGrip => aGrip.url == aUrl).pop(); + deferred.resolve(tabActor); + }); + + return deferred.promise; +} + +function getAddonActorForId(aClient, aAddonId) { + info("Get addon actor for ID: " + aAddonId); + let deferred = promise.defer(); + + aClient.listAddons(aResponse => { + let addonActor = aResponse.addons.filter(aGrip => aGrip.id == aAddonId).pop(); + info("got addon actor for ID: " + aAddonId); + deferred.resolve(addonActor); + }); + + return deferred.promise; +} + +function attachTabActorForUrl(aClient, aUrl) { + let deferred = promise.defer(); + + getTabActorForUrl(aClient, aUrl).then(aGrip => { + aClient.attachTab(aGrip.actor, aResponse => { + deferred.resolve([aGrip, aResponse]); + }); + }); + + return deferred.promise; +} + +function attachThreadActorForUrl(aClient, aUrl) { + let deferred = promise.defer(); + + attachTabActorForUrl(aClient, aUrl).then(([aGrip, aResponse]) => { + aClient.attachThread(aResponse.threadActor, (aResponse, aThreadClient) => { + aThreadClient.resume(aResponse => { + deferred.resolve(aThreadClient); + }); + }); + }); + + return deferred.promise; +} + +function once(aTarget, aEventName, aUseCapture = false) { + info("Waiting for event: '" + aEventName + "' on " + aTarget + "."); + + let deferred = promise.defer(); + + for (let [add, remove] of [ + ["addEventListener", "removeEventListener"], + ["addListener", "removeListener"], + ["on", "off"] + ]) { + if ((add in aTarget) && (remove in aTarget)) { + aTarget[add](aEventName, function onEvent(...aArgs) { + aTarget[remove](aEventName, onEvent, aUseCapture); + deferred.resolve.apply(deferred, aArgs); + }, aUseCapture); + break; + } + } + + return deferred.promise; +} + +function waitForTick() { + let deferred = promise.defer(); + executeSoon(deferred.resolve); + return deferred.promise; +} + +function waitForTime(aDelay) { + let deferred = promise.defer(); + setTimeout(deferred.resolve, aDelay); + return deferred.promise; +} + +function waitForSourceLoaded(aPanel, aUrl) { + let { Sources } = aPanel.panelWin.DebuggerView; + let isLoaded = Sources.items.some(item => + item.attachment.source.url === aUrl); + if (isLoaded) { + info("The correct source has been loaded."); + return promise.resolve(null); + } else { + return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.NEW_SOURCE).then(() => { + // Wait for it to be loaded in the UI and appear into Sources.items. + return waitForTick(); + }).then(() => { + return waitForSourceLoaded(aPanel, aUrl); + }); + } + +} + +function waitForSourceShown(aPanel, aUrl) { + return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.SOURCE_SHOWN).then(aSource => { + let sourceUrl = aSource.url || aSource.introductionUrl; + info("Source shown: " + sourceUrl); + + if (!sourceUrl.includes(aUrl)) { + return waitForSourceShown(aPanel, aUrl); + } else { + ok(true, "The correct source has been shown."); + } + }); +} + +function waitForEditorLocationSet(aPanel) { + return waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.EDITOR_LOCATION_SET); +} + +function ensureSourceIs(aPanel, aUrlOrSource, aWaitFlag = false) { + let sources = aPanel.panelWin.DebuggerView.Sources; + + if (sources.selectedValue === aUrlOrSource || + (sources.selectedItem && + sources.selectedItem.attachment.source.url.includes(aUrlOrSource))) { + ok(true, "Expected source is shown: " + aUrlOrSource); + return promise.resolve(null); + } + if (aWaitFlag) { + return waitForSourceShown(aPanel, aUrlOrSource); + } + ok(false, "Expected source was not already shown: " + aUrlOrSource); + return promise.reject(null); +} + +function waitForCaretUpdated(aPanel, aLine, aCol = 1) { + return waitForEditorEvents(aPanel, "cursorActivity").then(() => { + let cursor = aPanel.panelWin.DebuggerView.editor.getCursor(); + info("Caret updated: " + (cursor.line + 1) + ", " + (cursor.ch + 1)); + + if (!isCaretPos(aPanel, aLine, aCol)) { + return waitForCaretUpdated(aPanel, aLine, aCol); + } else { + ok(true, "The correct caret position has been set."); + } + }); +} + +function ensureCaretAt(aPanel, aLine, aCol = 1, aWaitFlag = false) { + if (isCaretPos(aPanel, aLine, aCol)) { + ok(true, "Expected caret position is set: " + aLine + "," + aCol); + return promise.resolve(null); + } + if (aWaitFlag) { + return waitForCaretUpdated(aPanel, aLine, aCol); + } + ok(false, "Expected caret position was not already set: " + aLine + "," + aCol); + return promise.reject(null); +} + +function isCaretPos(aPanel, aLine, aCol = 1) { + let editor = aPanel.panelWin.DebuggerView.editor; + let cursor = editor.getCursor(); + + // Source editor starts counting line and column numbers from 0. + info("Current editor caret position: " + (cursor.line + 1) + ", " + (cursor.ch + 1)); + return cursor.line == (aLine - 1) && cursor.ch == (aCol - 1); +} + +function isDebugPos(aPanel, aLine) { + let editor = aPanel.panelWin.DebuggerView.editor; + let location = editor.getDebugLocation(); + + // Source editor starts counting line and column numbers from 0. + info("Current editor debug position: " + (location + 1)); + return location != null && editor.hasLineClass(aLine - 1, "debug-line"); +} + +function isEditorSel(aPanel, [start, end]) { + let editor = aPanel.panelWin.DebuggerView.editor; + let range = { + start: editor.getOffset(editor.getCursor("start")), + end: editor.getOffset(editor.getCursor()) + }; + + // Source editor starts counting line and column numbers from 0. + info("Current editor selection: " + (range.start + 1) + ", " + (range.end + 1)); + return range.start == (start - 1) && range.end == (end - 1); +} + +function waitForSourceAndCaret(aPanel, aUrl, aLine, aCol) { + return promise.all([ + waitForSourceShown(aPanel, aUrl), + waitForCaretUpdated(aPanel, aLine, aCol) + ]); +} + +function waitForCaretAndScopes(aPanel, aLine, aCol) { + return promise.all([ + waitForCaretUpdated(aPanel, aLine, aCol), + waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.FETCHED_SCOPES) + ]); +} + +function waitForSourceAndCaretAndScopes(aPanel, aUrl, aLine, aCol) { + return promise.all([ + waitForSourceAndCaret(aPanel, aUrl, aLine, aCol), + waitForDebuggerEvents(aPanel, aPanel.panelWin.EVENTS.FETCHED_SCOPES) + ]); +} + +function waitForDebuggerEvents(aPanel, aEventName, aEventRepeat = 1) { + info("Waiting for debugger event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s)."); + + let deferred = promise.defer(); + let panelWin = aPanel.panelWin; + let count = 0; + + panelWin.on(aEventName, function onEvent(aEventName, ...aArgs) { + info("Debugger event '" + aEventName + "' fired: " + (++count) + " time(s)."); + + if (count == aEventRepeat) { + ok(true, "Enough '" + aEventName + "' panel events have been fired."); + panelWin.off(aEventName, onEvent); + deferred.resolve.apply(deferred, aArgs); + } + }); + + return deferred.promise; +} + +function waitForEditorEvents(aPanel, aEventName, aEventRepeat = 1) { + info("Waiting for editor event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s)."); + + let deferred = promise.defer(); + let editor = aPanel.panelWin.DebuggerView.editor; + let count = 0; + + editor.on(aEventName, function onEvent(...aArgs) { + info("Editor event '" + aEventName + "' fired: " + (++count) + " time(s)."); + + if (count == aEventRepeat) { + ok(true, "Enough '" + aEventName + "' editor events have been fired."); + editor.off(aEventName, onEvent); + deferred.resolve.apply(deferred, aArgs); + } + }); + + return deferred.promise; +} + +function waitForThreadEvents(aPanel, aEventName, aEventRepeat = 1) { + info("Waiting for thread event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s)."); + + let deferred = promise.defer(); + let thread = aPanel.panelWin.gThreadClient; + let count = 0; + + thread.addListener(aEventName, function onEvent(aEventName, ...aArgs) { + info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s)."); + + if (count == aEventRepeat) { + ok(true, "Enough '" + aEventName + "' thread events have been fired."); + thread.removeListener(aEventName, onEvent); + deferred.resolve.apply(deferred, aArgs); + } + }); + + return deferred.promise; +} + +function waitForClientEvents(aPanel, aEventName, aEventRepeat = 1) { + info("Waiting for client event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s)."); + + let deferred = promise.defer(); + let client = aPanel.panelWin.gClient; + let count = 0; + + client.addListener(aEventName, function onEvent(aEventName, ...aArgs) { + info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s)."); + + if (count == aEventRepeat) { + ok(true, "Enough '" + aEventName + "' thread events have been fired."); + client.removeListener(aEventName, onEvent); + deferred.resolve.apply(deferred, aArgs); + } + }); + + return deferred.promise; +} + +function ensureThreadClientState(aPanel, aState) { + let thread = aPanel.panelWin.gThreadClient; + let state = thread.state; + + info("Thread is: '" + state + "'."); + + if (state == aState) { + return promise.resolve(null); + } else { + return waitForThreadEvents(aPanel, aState); + } +} + +function reload(aPanel, aUrl) { + let activeTab = aPanel.panelWin.DebuggerController._target.activeTab; + aUrl ? activeTab.navigateTo(aUrl) : activeTab.reload(); +} + +function navigateActiveTabTo(aPanel, aUrl, aWaitForEventName, aEventRepeat) { + let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat); + reload(aPanel, aUrl); + return finished; +} + +function navigateActiveTabInHistory(aPanel, aDirection, aWaitForEventName, aEventRepeat) { + let finished = waitForDebuggerEvents(aPanel, aWaitForEventName, aEventRepeat); + content.history[aDirection](); + return finished; +} + +function reloadActiveTab(aPanel, aWaitForEventName, aEventRepeat) { + return navigateActiveTabTo(aPanel, null, aWaitForEventName, aEventRepeat); +} + +function clearText(aElement) { + info("Clearing text..."); + aElement.focus(); + aElement.value = ""; +} + +function setText(aElement, aText) { + clearText(aElement); + info("Setting text: " + aText); + aElement.value = aText; +} + +function typeText(aElement, aText) { + info("Typing text: " + aText); + aElement.focus(); + EventUtils.sendString(aText, aElement.ownerDocument.defaultView); +} + +function backspaceText(aElement, aTimes) { + info("Pressing backspace " + aTimes + " times."); + for (let i = 0; i < aTimes; i++) { + aElement.focus(); + EventUtils.sendKey("BACK_SPACE", aElement.ownerDocument.defaultView); + } +} + +function getTab(aTarget, aWindow) { + if (aTarget instanceof XULElement) { + return promise.resolve(aTarget); + } else { + return addTab(aTarget, aWindow); + } +} + +function getSources(aClient) { + info("Getting sources."); + + let deferred = promise.defer(); + + aClient.getSources((packet) => { + deferred.resolve(packet.sources); + }); + + return deferred.promise; +} + +/** + * Optionaly open a new tab and then open the debugger panel. + * The returned promise resolves only one the panel is fully set. + + * @param {String|xul:tab} urlOrTab + * If a string, consider it as the url of the tab to open before opening the + * debugger panel. + * Otherwise, if a <xul:tab>, do nothing, but open the debugger panel against + * the given tab. + * @param {Object} options + * Set of optional arguments: + * - {String} source + * If given, assert the default loaded source once the debugger is loaded. + * This string can be partial to only match a part of the source name. + * If null, do not expect any source and skip SOURCE_SHOWN wait. + * - {Number} line + * If given, wait for the caret to be set on a precise line + * + * @return {Promise} + * Resolves once debugger panel is fully set according to the given options. + */ +let initDebugger = Task.async(function*(urlOrTab, options) { + let { window, source, line } = options || {}; + info("Initializing a debugger panel."); + + let tab, url; + if (urlOrTab instanceof XULElement) { + // `urlOrTab` Is a Tab. + tab = urlOrTab; + } else { + // `urlOrTab` is an url. Open an empty tab first in order to load the page + // only once the panel is ready. That to be able to safely catch the + // SOURCE_SHOWN event. + tab = yield addTab("about:blank", window); + url = urlOrTab; + } + info("Debugee tab added successfully: " + urlOrTab); + + let debuggee = tab.linkedBrowser.contentWindow.wrappedJSObject; + let target = TargetFactory.forTab(tab); + + let toolbox = yield gDevTools.showToolbox(target, "jsdebugger"); + info("Debugger panel shown successfully."); + + let debuggerPanel = toolbox.getCurrentPanel(); + let panelWin = debuggerPanel.panelWin; + let { Sources } = panelWin.DebuggerView; + + prepareDebugger(debuggerPanel); + + if (url && url != "about:blank") { + let onCaretUpdated; + if (line) { + onCaretUpdated = waitForCaretUpdated(debuggerPanel, line); + } + if (source === null) { + // When there is no source in the document, we shouldn't wait for + // SOURCE_SHOWN event + yield reload(debuggerPanel, url); + } else { + yield navigateActiveTabTo(debuggerPanel, + url, + panelWin.EVENTS.SOURCE_SHOWN); + } + if (source) { + let isSelected = Sources.selectedItem.attachment.source.url === source; + if (!isSelected) { + // Ensure that the source is loaded first before trying to select it + yield waitForSourceLoaded(debuggerPanel, source); + // Select the js file. + let onSource = waitForSourceAndCaret(debuggerPanel, source, line ? line : 1); + Sources.selectedValue = getSourceActor(Sources, source); + yield onSource; + } + } + yield onCaretUpdated; + } + + return [tab, debuggee, debuggerPanel, window]; +}); + +// Creates an add-on debugger for a given add-on. The returned AddonDebugger +// object must be destroyed before finishing the test +function initAddonDebugger(aAddonId) { + let addonDebugger = new AddonDebugger(); + return addonDebugger.init(aAddonId).then(() => addonDebugger); +} + +function AddonDebugger() { + this._onMessage = this._onMessage.bind(this); + this._onConsoleAPICall = this._onConsoleAPICall.bind(this); + EventEmitter.decorate(this); +} + +AddonDebugger.prototype = { + init: Task.async(function* (aAddonId) { + info("Initializing an addon debugger panel."); + + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + DebuggerServer.allowChromeProcess = true; + + this.frame = document.createElement("iframe"); + this.frame.setAttribute("height", 400); + document.documentElement.appendChild(this.frame); + window.addEventListener("message", this._onMessage); + + let transport = DebuggerServer.connectPipe(); + this.client = new DebuggerClient(transport); + + yield this.client.connect(); + + let addonActor = yield getAddonActorForId(this.client, aAddonId); + + let targetOptions = { + form: addonActor, + client: this.client, + chrome: true, + isTabActor: false + }; + + let toolboxOptions = { + customIframe: this.frame + }; + + this.target = TargetFactory.forTab(targetOptions); + let toolbox = yield gDevTools.showToolbox(this.target, "jsdebugger", Toolbox.HostType.CUSTOM, toolboxOptions); + + info("Addon debugger panel shown successfully."); + + this.debuggerPanel = toolbox.getCurrentPanel(); + yield waitForSourceShown(this.debuggerPanel, ""); + + prepareDebugger(this.debuggerPanel); + yield this._attachConsole(); + }), + + destroy: Task.async(function* () { + yield this.client.close(); + yield this.debuggerPanel._toolbox.destroy(); + this.frame.remove(); + window.removeEventListener("message", this._onMessage); + }), + + _attachConsole: function () { + let deferred = promise.defer(); + this.client.attachConsole(this.target.form.consoleActor, ["ConsoleAPI"], (aResponse, aWebConsoleClient) => { + if (aResponse.error) { + deferred.reject(aResponse); + } + else { + this.webConsole = aWebConsoleClient; + this.client.addListener("consoleAPICall", this._onConsoleAPICall); + deferred.resolve(); + } + }); + return deferred.promise; + }, + + _onConsoleAPICall: function (aType, aPacket) { + if (aPacket.from != this.webConsole.actor) + return; + this.emit("console", aPacket.message); + }, + + /** + * Returns a list of the groups and sources in the UI. The returned array + * contains objects for each group with properties name and sources. The + * sources property contains an array with objects for each source for that + * group with properties label and url. + */ + getSourceGroups: Task.async(function* () { + let debuggerWin = this.debuggerPanel.panelWin; + let sources = yield getSources(debuggerWin.gThreadClient); + ok(sources.length, "retrieved sources"); + + // groups will be the return value, groupmap and the maps we put in it will + // be used as quick lookups to add the url information in below + let groups = []; + let groupmap = new Map(); + + let uigroups = this.debuggerPanel.panelWin.document.querySelectorAll(".side-menu-widget-group"); + for (let g of uigroups) { + let name = g.querySelector(".side-menu-widget-group-title .name").value; + let group = { + name: name, + sources: [] + }; + groups.push(group); + let labelmap = new Map(); + groupmap.set(name, labelmap); + + for (let l of g.querySelectorAll(".dbg-source-item")) { + let source = { + label: l.value, + url: null + }; + + labelmap.set(l.value, source); + group.sources.push(source); + } + } + + for (let source of sources) { + let { label, group } = debuggerWin.DebuggerView.Sources.getItemByValue(source.actor).attachment; + + if (!groupmap.has(group)) { + ok(false, "Saw a source group not in the UI: " + group); + continue; + } + + if (!groupmap.get(group).has(label)) { + ok(false, "Saw a source label not in the UI: " + label); + continue; + } + + groupmap.get(group).get(label).url = source.url.split(" -> ").pop(); + } + + return groups; + }), + + _onMessage: function (event) { + if (typeof(event.data) !== "string") { + return; + } + let json = JSON.parse(event.data); + switch (json.name) { + case "toolbox-title": + this.title = json.data.value; + break; + } + } +}; + +function initChromeDebugger(aOnClose) { + info("Initializing a chrome debugger process."); + + let deferred = promise.defer(); + + // Wait for the toolbox process to start... + BrowserToolboxProcess.init(aOnClose, (aEvent, aProcess) => { + info("Browser toolbox process started successfully."); + + prepareDebugger(aProcess); + deferred.resolve(aProcess); + }); + + return deferred.promise; +} + +function prepareDebugger(aDebugger) { + if ("target" in aDebugger) { + let view = aDebugger.panelWin.DebuggerView; + view.Variables.lazyEmpty = false; + view.Variables.lazySearch = false; + view.Filtering.FilteredSources._autoSelectFirstItem = true; + view.Filtering.FilteredFunctions._autoSelectFirstItem = true; + } else { + // Nothing to do here yet. + } +} + +function teardown(aPanel, aFlags = {}) { + info("Destroying the specified debugger."); + + let toolbox = aPanel._toolbox; + let tab = aPanel.target.tab; + let debuggerRootActorDisconnected = once(window, "Debugger:Shutdown"); + let debuggerPanelDestroyed = once(aPanel, "destroyed"); + let devtoolsToolboxDestroyed = toolbox.destroy(); + + return promise.all([ + debuggerRootActorDisconnected, + debuggerPanelDestroyed, + devtoolsToolboxDestroyed + ]).then(() => aFlags.noTabRemoval ? null : removeTab(tab)); +} + +function closeDebuggerAndFinish(aPanel, aFlags = {}) { + let thread = aPanel.panelWin.gThreadClient; + if (thread.state == "paused" && !aFlags.whilePaused) { + ok(false, "You should use 'resumeDebuggerThenCloseAndFinish' instead, " + + "unless you're absolutely sure about what you're doing."); + } + return teardown(aPanel, aFlags).then(finish); +} + +function resumeDebuggerThenCloseAndFinish(aPanel, aFlags = {}) { + let deferred = promise.defer(); + let thread = aPanel.panelWin.gThreadClient; + thread.resume(() => closeDebuggerAndFinish(aPanel, aFlags).then(deferred.resolve)); + return deferred.promise; +} + +// Blackboxing helpers + +function getBlackBoxButton(aPanel) { + return aPanel.panelWin.document.getElementById("black-box"); +} + +/** + * Returns the node that has the black-boxed class applied to it. + */ +function getSelectedSourceElement(aPanel) { + return aPanel.panelWin.DebuggerView.Sources.selectedItem.prebuiltNode; +} + +function toggleBlackBoxing(aPanel, aSourceActor = null) { + function clickBlackBoxButton() { + getBlackBoxButton(aPanel).click(); + } + + const blackBoxChanged = waitForDispatch( + aPanel, + aPanel.panelWin.constants.BLACKBOX + ).then(() => { + return aSourceActor ? + getSource(aPanel, aSourceActor) : + getSelectedSource(aPanel); + }); + + if (aSourceActor) { + aPanel.panelWin.DebuggerView.Sources.selectedValue = aSourceActor; + ensureSourceIs(aPanel, aSourceActor, true).then(clickBlackBoxButton); + } else { + clickBlackBoxButton(); + } + + return blackBoxChanged; +} + +function selectSourceAndGetBlackBoxButton(aPanel, aUrl) { + function returnBlackboxButton() { + return getBlackBoxButton(aPanel); + } + + let sources = aPanel.panelWin.DebuggerView.Sources; + sources.selectedValue = getSourceActor(sources, aUrl); + return ensureSourceIs(aPanel, aUrl, true).then(returnBlackboxButton); +} + +// Variables view inspection popup helpers + +function openVarPopup(aPanel, aCoords, aWaitForFetchedProperties) { + let events = aPanel.panelWin.EVENTS; + let editor = aPanel.panelWin.DebuggerView.editor; + let bubble = aPanel.panelWin.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + + let popupShown = once(tooltip, "popupshown"); + let fetchedProperties = aWaitForFetchedProperties + ? waitForDebuggerEvents(aPanel, events.FETCHED_BUBBLE_PROPERTIES) + : promise.resolve(null); + let updatedFrame = waitForDebuggerEvents(aPanel, events.FETCHED_SCOPES); + + let { left, top } = editor.getCoordsFromPosition(aCoords); + bubble._findIdentifier(left, top); + return promise.all([popupShown, fetchedProperties, updatedFrame]).then(waitForTick); +} + +// Simulates the mouse hovering a variable in the debugger +// Takes in account the position of the cursor in the text, if the text is +// selected and if a button is currently pushed (aButtonPushed > 0). +// The function returns a promise which returns true if the popup opened or +// false if it didn't +function intendOpenVarPopup(aPanel, aPosition, aButtonPushed) { + let bubble = aPanel.panelWin.DebuggerView.VariableBubble; + let editor = aPanel.panelWin.DebuggerView.editor; + let tooltip = bubble._tooltip; + + let { left, top } = editor.getCoordsFromPosition(aPosition); + + const eventDescriptor = { + clientX: left, + clientY: top, + buttons: aButtonPushed + }; + + bubble._onMouseMove(eventDescriptor); + + const deferred = promise.defer(); + window.setTimeout( + function () { + if (tooltip.isEmpty()) { + deferred.resolve(false); + } else { + deferred.resolve(true); + } + }, + bubble.TOOLTIP_SHOW_DELAY + 1000 + ); + + return deferred.promise; +} + +function hideVarPopup(aPanel) { + let bubble = aPanel.panelWin.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + + let popupHiding = once(tooltip, "popuphiding"); + bubble.hideContents(); + return popupHiding.then(waitForTick); +} + +function hideVarPopupByScrollingEditor(aPanel) { + let editor = aPanel.panelWin.DebuggerView.editor; + let bubble = aPanel.panelWin.DebuggerView.VariableBubble; + let tooltip = bubble._tooltip.panel; + + let popupHiding = once(tooltip, "popuphiding"); + editor.setFirstVisibleLine(0); + return popupHiding.then(waitForTick); +} + +function reopenVarPopup(...aArgs) { + return hideVarPopup.apply(this, aArgs).then(() => openVarPopup.apply(this, aArgs)); +} + +function attachAddonActorForId(aClient, aAddonId) { + let deferred = promise.defer(); + + getAddonActorForId(aClient, aAddonId).then(aGrip => { + aClient.attachAddon(aGrip.actor, aResponse => { + deferred.resolve([aGrip, aResponse]); + }); + }); + + return deferred.promise; +} + +function doResume(aPanel) { + const threadClient = aPanel.panelWin.gThreadClient; + return threadClient.resume(); +} + +function doInterrupt(aPanel) { + const threadClient = aPanel.panelWin.gThreadClient; + return threadClient.interrupt(); +} + +function pushPrefs(...aPrefs) { + let deferred = promise.defer(); + SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve); + return deferred.promise; +} + +function popPrefs() { + let deferred = promise.defer(); + SpecialPowers.popPrefEnv(deferred.resolve); + return deferred.promise; +} + +// Source helpers + +function getSelectedSource(panel) { + const win = panel.panelWin; + return win.queries.getSelectedSource(win.DebuggerController.getState()); +} + +function getSource(panel, actor) { + const win = panel.panelWin; + return win.queries.getSource(win.DebuggerController.getState(), actor); +} + +function getSelectedSourceURL(aSources) { + return (aSources.selectedItem && + aSources.selectedItem.attachment.source.url); +} + +function getSourceURL(aSources, aActor) { + let item = aSources.getItemByValue(aActor); + return item && item.attachment.source.url; +} + +function getSourceActor(aSources, aURL) { + let item = aSources.getItemForAttachment(a => a.source && a.source.url === aURL); + return item && item.value; +} + +function getSourceForm(aSources, aURL) { + let item = aSources.getItemByValue(getSourceActor(aSources, aURL)); + return item.attachment.source; +} + +var nextId = 0; + +function jsonrpc(tab, method, params) { + return new Promise(function (resolve, reject) { + let currentId = nextId++; + let messageManager = tab.linkedBrowser.messageManager; + messageManager.sendAsyncMessage("jsonrpc", { + method: method, + params: params, + id: currentId + }); + messageManager.addMessageListener("jsonrpc", function listener(res) { + const { data: { result, error, id } } = res; + if (id !== currentId) { + return; + } + + messageManager.removeMessageListener("jsonrpc", listener); + if (error != null) { + reject(error); + } + + resolve(result); + }); + }); +} + +function callInTab(tab, name) { + info("Calling function with name '" + name + "' in tab."); + + return jsonrpc(tab, "call", [name, Array.prototype.slice.call(arguments, 2)]); +} + +function evalInTab(tab, string) { + info("Evalling string in tab."); + + return jsonrpc(tab, "_eval", [string]); +} + +function createWorkerInTab(tab, url) { + info("Creating worker with url '" + url + "' in tab."); + + return jsonrpc(tab, "createWorker", [url]); +} + +function terminateWorkerInTab(tab, url) { + info("Terminating worker with url '" + url + "' in tab."); + + return jsonrpc(tab, "terminateWorker", [url]); +} + +function postMessageToWorkerInTab(tab, url, message) { + info("Posting message to worker with url '" + url + "' in tab."); + + return jsonrpc(tab, "postMessageToWorker", [url, message]); +} + +function generateMouseClickInTab(tab, path) { + info("Generating mouse click in tab."); + + return jsonrpc(tab, "generateMouseClick", [path]); +} + +function connect(client) { + info("Connecting client."); + return client.connect(); +} + +function close(client) { + info("Waiting for client to close.\n"); + return client.close(); +} + +function listTabs(client) { + info("Listing tabs."); + return client.listTabs(); +} + +function findTab(tabs, url) { + info("Finding tab with url '" + url + "'."); + for (let tab of tabs) { + if (tab.url === url) { + return tab; + } + } + return null; +} + +function attachTab(client, tab) { + info("Attaching to tab with url '" + tab.url + "'."); + return new Promise(function (resolve) { + client.attachTab(tab.actor, function (response, tabClient) { + resolve([response, tabClient]); + }); + }); +} + +function listWorkers(tabClient) { + info("Listing workers."); + return new Promise(function (resolve) { + tabClient.listWorkers(function (response) { + resolve(response); + }); + }); +} + +function findWorker(workers, url) { + info("Finding worker with url '" + url + "'."); + for (let worker of workers) { + if (worker.url === url) { + return worker; + } + } + return null; +} + +function attachWorker(tabClient, worker) { + info("Attaching to worker with url '" + worker.url + "'."); + return new Promise(function (resolve, reject) { + tabClient.attachWorker(worker.actor, function (response, workerClient) { + resolve([response, workerClient]); + }); + }); +} + +function waitForWorkerListChanged(tabClient) { + info("Waiting for worker list to change."); + return new Promise(function (resolve) { + tabClient.addListener("workerListChanged", function listener() { + tabClient.removeListener("workerListChanged", listener); + resolve(); + }); + }); +} + +function attachThread(workerClient, options) { + info("Attaching to thread."); + return new Promise(function (resolve, reject) { + workerClient.attachThread(options, function (response, threadClient) { + resolve([response, threadClient]); + }); + }); +} + +function waitForWorkerClose(workerClient) { + info("Waiting for worker to close."); + return new Promise(function (resolve) { + workerClient.addOneTimeListener("close", function () { + info("Worker did close."); + resolve(); + }); + }); +} + +function resume(threadClient) { + info("Resuming thread."); + return threadClient.resume(); +} + +function findSource(sources, url) { + info("Finding source with url '" + url + "'.\n"); + for (let source of sources) { + if (source.url === url) { + return source; + } + } + return null; +} + +function waitForEvent(client, type, predicate) { + return new Promise(function (resolve) { + function listener(type, packet) { + if (!predicate(packet)) { + return; + } + client.removeListener(listener); + resolve(packet); + } + + if (predicate) { + client.addListener(type, listener); + } else { + client.addOneTimeListener(type, function (type, packet) { + resolve(packet); + }); + } + }); +} + +function waitForPause(threadClient) { + info("Waiting for pause.\n"); + return waitForEvent(threadClient, "paused"); +} + +function setBreakpoint(sourceClient, location) { + info("Setting breakpoint.\n"); + return sourceClient.setBreakpoint(location); +} + +function source(sourceClient) { + info("Getting source.\n"); + return sourceClient.source(); +} + +// Return a promise with a reference to jsterm, opening the split +// console if necessary. This cleans up the split console pref so +// it won't pollute other tests. +function getSplitConsole(toolbox, win) { + registerCleanupFunction(() => { + Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled"); + }); + + if (!win) { + win = toolbox.win; + } + + if (!toolbox.splitConsole) { + EventUtils.synthesizeKey("VK_ESCAPE", {}, win); + } + + return new Promise(resolve => { + toolbox.getPanelWhenReady("webconsole").then(() => { + ok(toolbox.splitConsole, "Split console is shown."); + let jsterm = toolbox.getPanel("webconsole").hud.jsterm; + resolve(jsterm); + }); + }); +} + +// navigation + +function waitForNavigation(gPanel) { + const target = gPanel.panelWin.gTarget; + const deferred = promise.defer(); + target.once("navigate", () => { + deferred.resolve(); + }); + info("Waiting for navigation..."); + return deferred.promise; +} + +// actions + +function bindActionCreators(panel) { + const win = panel.panelWin; + const dispatch = win.DebuggerController.dispatch; + const { bindActionCreators } = win.require("devtools/client/shared/vendor/redux"); + return bindActionCreators(win.actions, dispatch); +} + +// Wait until an action of `type` is dispatched. This is different +// then `_afterDispatchDone` because it doesn't wait for async actions +// to be done/errored. Use this if you want to listen for the "start" +// action of an async operation (somewhat rare). +function waitForNextDispatch(store, type) { + return new Promise(resolve => { + store.dispatch({ + // Normally we would use `services.WAIT_UNTIL`, but use the + // internal name here so tests aren't forced to always pass it + // in + type: "@@service/waitUntil", + predicate: action => action.type === type, + run: (dispatch, getState, action) => { + resolve(action); + } + }); + }); +} + +// Wait until an action of `type` is dispatched. If it's part of an +// async operation, wait until the `status` field is "done" or "error" +function _afterDispatchDone(store, type) { + return new Promise(resolve => { + store.dispatch({ + // Normally we would use `services.WAIT_UNTIL`, but use the + // internal name here so tests aren't forced to always pass it + // in + type: "@@service/waitUntil", + predicate: action => { + if (action.type === type) { + return action.status ? + (action.status === "done" || action.status === "error") : + true; + } + }, + run: (dispatch, getState, action) => { + resolve(action); + } + }); + }); +} + +function waitForDispatch(panel, type, eventRepeat = 1) { + const controller = panel.panelWin.DebuggerController; + const actionType = panel.panelWin.constants[type]; + let count = 0; + + return Task.spawn(function* () { + info("Waiting for " + type + " to dispatch " + eventRepeat + " time(s)"); + while (count < eventRepeat) { + yield _afterDispatchDone(controller, actionType); + count++; + info(type + " dispatched " + count + " time(s)"); + } + }); +} + +function* initWorkerDebugger(TAB_URL, WORKER_URL) { + if (!DebuggerServer.initialized) { + DebuggerServer.init(); + DebuggerServer.addBrowserActors(); + } + + let client = new DebuggerClient(DebuggerServer.connectPipe()); + yield connect(client); + + let tab = yield addTab(TAB_URL); + let { tabs } = yield listTabs(client); + let [, tabClient] = yield attachTab(client, findTab(tabs, TAB_URL)); + + yield createWorkerInTab(tab, WORKER_URL); + + let { workers } = yield listWorkers(tabClient); + let [, workerClient] = yield attachWorker(tabClient, + findWorker(workers, WORKER_URL)); + + let toolbox = yield gDevTools.showToolbox(TargetFactory.forWorker(workerClient), + "jsdebugger", + Toolbox.HostType.WINDOW); + + let debuggerPanel = toolbox.getCurrentPanel(); + let gDebugger = debuggerPanel.panelWin; + + return {client, tab, tabClient, workerClient, toolbox, gDebugger}; +} + diff --git a/devtools/client/debugger/test/mochitest/sjs_post-page.sjs b/devtools/client/debugger/test/mochitest/sjs_post-page.sjs new file mode 100644 index 000000000..06f7c60d0 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/sjs_post-page.sjs @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const CC = Components.Constructor; +const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream"); + +function handleRequest(request, response) +{ + let method = request.method; + let body = "<script>\"" + method + "\";</script>"; + body += "<form method=\"POST\"><input type=\"submit\"></form>"; + response.bodyOutputStream.write(body, body.length); +} diff --git a/devtools/client/debugger/test/mochitest/sjs_random-javascript.sjs b/devtools/client/debugger/test/mochitest/sjs_random-javascript.sjs new file mode 100644 index 000000000..3e0ea8e53 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/sjs_random-javascript.sjs @@ -0,0 +1,11 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function handleRequest(request, response) { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/javascript; charset=utf-8", false); + response.write([ + "window.setInterval(function bacon() {", + " var x = '" + Math.random() + "';", + "}, 0);"].join("\n")); +} diff --git a/devtools/client/debugger/test/mochitest/testactors.js b/devtools/client/debugger/test/mochitest/testactors.js new file mode 100644 index 000000000..f7583b615 --- /dev/null +++ b/devtools/client/debugger/test/mochitest/testactors.js @@ -0,0 +1,33 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function TestActor1(aConnection, aTab) +{ + this.conn = aConnection; + this.tab = aTab; +} + +TestActor1.prototype = { + actorPrefix: "test_one", + + grip: function TA1_grip() { + return { actor: this.actorID, + test: "TestActor1" }; + }, + + onPing: function TA1_onPing() { + return { pong: "pong" }; + } +}; + +TestActor1.prototype.requestTypes = { + "ping": TestActor1.prototype.onPing +}; + +DebuggerServer.removeTabActor(TestActor1); +DebuggerServer.removeGlobalActor(TestActor1); + +DebuggerServer.addTabActor(TestActor1, "testTabActor1"); +DebuggerServer.addGlobalActor(TestActor1, "testGlobalActor1"); |