summaryrefslogtreecommitdiffstats
path: root/devtools/server/tests/unit
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/tests/unit')
-rw-r--r--devtools/server/tests/unit/.eslintrc.js6
-rw-r--r--devtools/server/tests/unit/addons/web-extension-upgrade/manifest.json10
-rw-r--r--devtools/server/tests/unit/addons/web-extension/manifest.json10
-rw-r--r--devtools/server/tests/unit/addons/web-extension2/manifest.json10
-rw-r--r--devtools/server/tests/unit/babel_and_browserify_script_with_source_map.js79
-rw-r--r--devtools/server/tests/unit/head_dbg.js862
-rw-r--r--devtools/server/tests/unit/hello-actor.js18
-rw-r--r--devtools/server/tests/unit/post_init_global_actors.js17
-rw-r--r--devtools/server/tests/unit/post_init_tab_actors.js17
-rw-r--r--devtools/server/tests/unit/pre_init_global_actors.js17
-rw-r--r--devtools/server/tests/unit/pre_init_tab_actors.js17
-rw-r--r--devtools/server/tests/unit/registertestactors-01.js15
-rw-r--r--devtools/server/tests/unit/registertestactors-02.js15
-rw-r--r--devtools/server/tests/unit/registertestactors-03.js40
-rw-r--r--devtools/server/tests/unit/setBreakpoint-on-column-in-gcd-script.js7
-rw-r--r--devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-at-end-of-line.js6
-rw-r--r--devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-in-gcd-script.js7
-rw-r--r--devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets.js5
-rw-r--r--devtools/server/tests/unit/setBreakpoint-on-column.js5
-rw-r--r--devtools/server/tests/unit/setBreakpoint-on-line-in-gcd-script.js9
-rw-r--r--devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-offsets.js7
-rw-r--r--devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-statements.js5
-rw-r--r--devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets-in-gcd-script.js9
-rw-r--r--devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets.js7
-rw-r--r--devtools/server/tests/unit/setBreakpoint-on-line.js7
-rw-r--r--devtools/server/tests/unit/source-map-data/sourcemapped.coffee6
-rw-r--r--devtools/server/tests/unit/source-map-data/sourcemapped.map10
-rw-r--r--devtools/server/tests/unit/sourcemapped.js16
-rw-r--r--devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_01.js18
-rw-r--r--devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_02.js20
-rw-r--r--devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_03.js18
-rw-r--r--devtools/server/tests/unit/test_actor-registry-actor.js80
-rw-r--r--devtools/server/tests/unit/test_add_actors.js107
-rw-r--r--devtools/server/tests/unit/test_addon_reload.js98
-rw-r--r--devtools/server/tests/unit/test_addons_actor.js51
-rw-r--r--devtools/server/tests/unit/test_animation_name.js87
-rw-r--r--devtools/server/tests/unit/test_animation_type.js68
-rw-r--r--devtools/server/tests/unit/test_attach.js37
-rw-r--r--devtools/server/tests/unit/test_blackboxing-01.js145
-rw-r--r--devtools/server/tests/unit/test_blackboxing-02.js113
-rw-r--r--devtools/server/tests/unit/test_blackboxing-03.js102
-rw-r--r--devtools/server/tests/unit/test_blackboxing-04.js88
-rw-r--r--devtools/server/tests/unit/test_blackboxing-05.js84
-rw-r--r--devtools/server/tests/unit/test_blackboxing-06.js102
-rw-r--r--devtools/server/tests/unit/test_blackboxing-07.js62
-rw-r--r--devtools/server/tests/unit/test_breakpoint-01.js75
-rw-r--r--devtools/server/tests/unit/test_breakpoint-02.js67
-rw-r--r--devtools/server/tests/unit/test_breakpoint-03.js96
-rw-r--r--devtools/server/tests/unit/test_breakpoint-04.js80
-rw-r--r--devtools/server/tests/unit/test_breakpoint-05.js82
-rw-r--r--devtools/server/tests/unit/test_breakpoint-06.js89
-rw-r--r--devtools/server/tests/unit/test_breakpoint-07.js85
-rw-r--r--devtools/server/tests/unit/test_breakpoint-08.js96
-rw-r--r--devtools/server/tests/unit/test_breakpoint-09.js88
-rw-r--r--devtools/server/tests/unit/test_breakpoint-10.js89
-rw-r--r--devtools/server/tests/unit/test_breakpoint-11.js88
-rw-r--r--devtools/server/tests/unit/test_breakpoint-12.js113
-rw-r--r--devtools/server/tests/unit/test_breakpoint-13.js115
-rw-r--r--devtools/server/tests/unit/test_breakpoint-14.js113
-rw-r--r--devtools/server/tests/unit/test_breakpoint-15.js69
-rw-r--r--devtools/server/tests/unit/test_breakpoint-16.js83
-rw-r--r--devtools/server/tests/unit/test_breakpoint-17.js120
-rw-r--r--devtools/server/tests/unit/test_breakpoint-18.js82
-rw-r--r--devtools/server/tests/unit/test_breakpoint-19.js70
-rw-r--r--devtools/server/tests/unit/test_breakpoint-20.js108
-rw-r--r--devtools/server/tests/unit/test_breakpoint-21.js85
-rw-r--r--devtools/server/tests/unit/test_breakpoint-actor-map.js180
-rw-r--r--devtools/server/tests/unit/test_client_close.js39
-rw-r--r--devtools/server/tests/unit/test_client_request.js214
-rw-r--r--devtools/server/tests/unit/test_conditional_breakpoint-01.js61
-rw-r--r--devtools/server/tests/unit/test_conditional_breakpoint-02.js60
-rw-r--r--devtools/server/tests/unit/test_conditional_breakpoint-03.js61
-rw-r--r--devtools/server/tests/unit/test_dbgactor.js116
-rw-r--r--devtools/server/tests/unit/test_dbgclient_debuggerstatement.js73
-rw-r--r--devtools/server/tests/unit/test_dbgglobal.js62
-rw-r--r--devtools/server/tests/unit/test_eval-01.js58
-rw-r--r--devtools/server/tests/unit/test_eval-02.js48
-rw-r--r--devtools/server/tests/unit/test_eval-03.js50
-rw-r--r--devtools/server/tests/unit/test_eval-04.js69
-rw-r--r--devtools/server/tests/unit/test_eval-05.js54
-rw-r--r--devtools/server/tests/unit/test_eventlooplag_actor.js59
-rw-r--r--devtools/server/tests/unit/test_forwardingprefix.js196
-rw-r--r--devtools/server/tests/unit/test_frameactor-01.js43
-rw-r--r--devtools/server/tests/unit/test_frameactor-02.js45
-rw-r--r--devtools/server/tests/unit/test_frameactor-03.js47
-rw-r--r--devtools/server/tests/unit/test_frameactor-04.js91
-rw-r--r--devtools/server/tests/unit/test_frameactor-05.js89
-rw-r--r--devtools/server/tests/unit/test_framearguments-01.js51
-rw-r--r--devtools/server/tests/unit/test_framebindings-01.js77
-rw-r--r--devtools/server/tests/unit/test_framebindings-02.js63
-rw-r--r--devtools/server/tests/unit/test_framebindings-03.js69
-rw-r--r--devtools/server/tests/unit/test_framebindings-04.js84
-rw-r--r--devtools/server/tests/unit/test_framebindings-05.js63
-rw-r--r--devtools/server/tests/unit/test_framebindings-06.js60
-rw-r--r--devtools/server/tests/unit/test_framebindings-07.js64
-rw-r--r--devtools/server/tests/unit/test_frameclient-01.js53
-rw-r--r--devtools/server/tests/unit/test_frameclient-02.js46
-rw-r--r--devtools/server/tests/unit/test_functiongrips-01.js95
-rw-r--r--devtools/server/tests/unit/test_get-executable-lines-source-map.js56
-rw-r--r--devtools/server/tests/unit/test_get-executable-lines.js55
-rw-r--r--devtools/server/tests/unit/test_getRuleText.js137
-rw-r--r--devtools/server/tests/unit/test_getTextAtLineColumn.js35
-rw-r--r--devtools/server/tests/unit/test_getyoungestframe.js30
-rw-r--r--devtools/server/tests/unit/test_ignore_caught_exceptions.js50
-rw-r--r--devtools/server/tests/unit/test_ignore_no_interface_exceptions.js54
-rw-r--r--devtools/server/tests/unit/test_interrupt.js50
-rw-r--r--devtools/server/tests/unit/test_layout-reflows-observer.js286
-rw-r--r--devtools/server/tests/unit/test_listsources-01.js59
-rw-r--r--devtools/server/tests/unit/test_listsources-02.js49
-rw-r--r--devtools/server/tests/unit/test_listsources-03.js52
-rw-r--r--devtools/server/tests/unit/test_listsources-04.js58
-rw-r--r--devtools/server/tests/unit/test_longstringactor.js104
-rw-r--r--devtools/server/tests/unit/test_longstringgrips-01.js71
-rw-r--r--devtools/server/tests/unit/test_longstringgrips-02.js60
-rw-r--r--devtools/server/tests/unit/test_monitor_actor.js76
-rw-r--r--devtools/server/tests/unit/test_nativewrappers.js30
-rw-r--r--devtools/server/tests/unit/test_nesting-01.js48
-rw-r--r--devtools/server/tests/unit/test_nesting-02.js81
-rw-r--r--devtools/server/tests/unit/test_nesting-03.js51
-rw-r--r--devtools/server/tests/unit/test_new_source-01.js40
-rw-r--r--devtools/server/tests/unit/test_nodelistactor.js26
-rw-r--r--devtools/server/tests/unit/test_nsjsinspector.js59
-rw-r--r--devtools/server/tests/unit/test_objectgrips-01.js58
-rw-r--r--devtools/server/tests/unit/test_objectgrips-02.js65
-rw-r--r--devtools/server/tests/unit/test_objectgrips-03.js73
-rw-r--r--devtools/server/tests/unit/test_objectgrips-04.js76
-rw-r--r--devtools/server/tests/unit/test_objectgrips-05.js66
-rw-r--r--devtools/server/tests/unit/test_objectgrips-06.js66
-rw-r--r--devtools/server/tests/unit/test_objectgrips-07.js74
-rw-r--r--devtools/server/tests/unit/test_objectgrips-08.js72
-rw-r--r--devtools/server/tests/unit/test_objectgrips-09.js74
-rw-r--r--devtools/server/tests/unit/test_objectgrips-10.js72
-rw-r--r--devtools/server/tests/unit/test_objectgrips-11.js52
-rw-r--r--devtools/server/tests/unit/test_objectgrips-12.js162
-rw-r--r--devtools/server/tests/unit/test_objectgrips-13.js66
-rw-r--r--devtools/server/tests/unit/test_pause_exceptions-01.js50
-rw-r--r--devtools/server/tests/unit/test_pause_exceptions-02.js47
-rw-r--r--devtools/server/tests/unit/test_pauselifetime-01.js54
-rw-r--r--devtools/server/tests/unit/test_pauselifetime-02.js56
-rw-r--r--devtools/server/tests/unit/test_pauselifetime-03.js61
-rw-r--r--devtools/server/tests/unit/test_pauselifetime-04.js48
-rw-r--r--devtools/server/tests/unit/test_profiler_activation-01.js89
-rw-r--r--devtools/server/tests/unit/test_profiler_activation-02.js46
-rw-r--r--devtools/server/tests/unit/test_profiler_bufferstatus.js127
-rw-r--r--devtools/server/tests/unit/test_profiler_close.js69
-rw-r--r--devtools/server/tests/unit/test_profiler_data.js110
-rw-r--r--devtools/server/tests/unit/test_profiler_events-01.js62
-rw-r--r--devtools/server/tests/unit/test_profiler_events-02.js70
-rw-r--r--devtools/server/tests/unit/test_profiler_getbufferinfo.js123
-rw-r--r--devtools/server/tests/unit/test_profiler_getfeatures.js35
-rw-r--r--devtools/server/tests/unit/test_profiler_getsharedlibraryinformation.js45
-rw-r--r--devtools/server/tests/unit/test_promise_state-01.js40
-rw-r--r--devtools/server/tests/unit/test_promise_state-02.js45
-rw-r--r--devtools/server/tests/unit/test_promise_state-03.js45
-rw-r--r--devtools/server/tests/unit/test_promises_actor_attach.js52
-rw-r--r--devtools/server/tests/unit/test_promises_actor_exist.js29
-rw-r--r--devtools/server/tests/unit/test_promises_actor_list_promises.js63
-rw-r--r--devtools/server/tests/unit/test_promises_actor_onnewpromise.js72
-rw-r--r--devtools/server/tests/unit/test_promises_actor_onpromisesettled.js92
-rw-r--r--devtools/server/tests/unit/test_promises_client_getdependentpromises.js112
-rw-r--r--devtools/server/tests/unit/test_promises_object_creationtimestamp.js71
-rw-r--r--devtools/server/tests/unit/test_promises_object_timetosettle-01.js80
-rw-r--r--devtools/server/tests/unit/test_promises_object_timetosettle-02.js74
-rw-r--r--devtools/server/tests/unit/test_protocolSpec.js17
-rw-r--r--devtools/server/tests/unit/test_protocol_abort.js83
-rw-r--r--devtools/server/tests/unit/test_protocol_async.js184
-rw-r--r--devtools/server/tests/unit/test_protocol_children.js559
-rw-r--r--devtools/server/tests/unit/test_protocol_formtype.js177
-rw-r--r--devtools/server/tests/unit/test_protocol_longstring.js218
-rw-r--r--devtools/server/tests/unit/test_protocol_simple.js319
-rw-r--r--devtools/server/tests/unit/test_protocol_stack.js98
-rw-r--r--devtools/server/tests/unit/test_protocol_unregister.js44
-rw-r--r--devtools/server/tests/unit/test_reattach-thread.js58
-rw-r--r--devtools/server/tests/unit/test_registerClient.js95
-rw-r--r--devtools/server/tests/unit/test_register_actor.js113
-rw-r--r--devtools/server/tests/unit/test_requestTypes.js36
-rw-r--r--devtools/server/tests/unit/test_safe-getter.js25
-rw-r--r--devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js58
-rw-r--r--devtools/server/tests/unit/test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js39
-rw-r--r--devtools/server/tests/unit/test_setBreakpoint-on-column.js57
-rw-r--r--devtools/server/tests/unit/test_setBreakpoint-on-line-in-gcd-script.js57
-rw-r--r--devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js70
-rw-r--r--devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-statements.js57
-rw-r--r--devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js58
-rw-r--r--devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets.js57
-rw-r--r--devtools/server/tests/unit/test_setBreakpoint-on-line.js57
-rw-r--r--devtools/server/tests/unit/test_source-01.js78
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-01.js64
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-02.js67
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-03.js137
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-04.js46
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-05.js46
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-06.js94
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-07.js67
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-08.js50
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-09.js95
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-10.js73
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-11.js83
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-12.js75
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-13.js105
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-16.js46
-rw-r--r--devtools/server/tests/unit/test_sourcemaps-17.js63
-rw-r--r--devtools/server/tests/unit/test_stepping-01.js83
-rw-r--r--devtools/server/tests/unit/test_stepping-02.js83
-rw-r--r--devtools/server/tests/unit/test_stepping-03.js62
-rw-r--r--devtools/server/tests/unit/test_stepping-04.js74
-rw-r--r--devtools/server/tests/unit/test_stepping-05.js101
-rw-r--r--devtools/server/tests/unit/test_stepping-06.js99
-rw-r--r--devtools/server/tests/unit/test_stepping-07.js92
-rw-r--r--devtools/server/tests/unit/test_symbols-01.js58
-rw-r--r--devtools/server/tests/unit/test_symbols-02.js49
-rw-r--r--devtools/server/tests/unit/test_threadlifetime-01.js58
-rw-r--r--devtools/server/tests/unit/test_threadlifetime-02.js59
-rw-r--r--devtools/server/tests/unit/test_threadlifetime-03.js81
-rw-r--r--devtools/server/tests/unit/test_threadlifetime-04.js53
-rw-r--r--devtools/server/tests/unit/test_threadlifetime-05.js83
-rw-r--r--devtools/server/tests/unit/test_threadlifetime-06.js71
-rw-r--r--devtools/server/tests/unit/test_unsafeDereference.js134
-rw-r--r--devtools/server/tests/unit/test_xpcshell_debugging.js48
-rw-r--r--devtools/server/tests/unit/testactors.js176
-rw-r--r--devtools/server/tests/unit/tracerlocations.js8
-rw-r--r--devtools/server/tests/unit/xpcshell.ini232
-rw-r--r--devtools/server/tests/unit/xpcshell_debugging_script.js9
223 files changed, 16750 insertions, 0 deletions
diff --git a/devtools/server/tests/unit/.eslintrc.js b/devtools/server/tests/unit/.eslintrc.js
new file mode 100644
index 000000000..012428019
--- /dev/null
+++ b/devtools/server/tests/unit/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools xpcshell eslintrc config.
+ "extends": "../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/server/tests/unit/addons/web-extension-upgrade/manifest.json b/devtools/server/tests/unit/addons/web-extension-upgrade/manifest.json
new file mode 100644
index 000000000..f70b11efd
--- /dev/null
+++ b/devtools/server/tests/unit/addons/web-extension-upgrade/manifest.json
@@ -0,0 +1,10 @@
+{
+ "manifest_version": 2,
+ "name": "Test Addons Actor Upgrade",
+ "version": "1.0",
+ "applications": {
+ "gecko": {
+ "id": "test-addons-actor@mozilla.org"
+ }
+ }
+}
diff --git a/devtools/server/tests/unit/addons/web-extension/manifest.json b/devtools/server/tests/unit/addons/web-extension/manifest.json
new file mode 100644
index 000000000..d120cf3da
--- /dev/null
+++ b/devtools/server/tests/unit/addons/web-extension/manifest.json
@@ -0,0 +1,10 @@
+{
+ "manifest_version": 2,
+ "name": "Test Addons Actor",
+ "version": "1.0",
+ "applications": {
+ "gecko": {
+ "id": "test-addons-actor@mozilla.org"
+ }
+ }
+}
diff --git a/devtools/server/tests/unit/addons/web-extension2/manifest.json b/devtools/server/tests/unit/addons/web-extension2/manifest.json
new file mode 100644
index 000000000..57daae29d
--- /dev/null
+++ b/devtools/server/tests/unit/addons/web-extension2/manifest.json
@@ -0,0 +1,10 @@
+{
+ "manifest_version": 2,
+ "name": "Test Addons Actor 2",
+ "version": "1.0",
+ "applications": {
+ "gecko": {
+ "id": "test-addons-actor2@mozilla.org"
+ }
+ }
+}
diff --git a/devtools/server/tests/unit/babel_and_browserify_script_with_source_map.js b/devtools/server/tests/unit/babel_and_browserify_script_with_source_map.js
new file mode 100644
index 000000000..317eb68ca
--- /dev/null
+++ b/devtools/server/tests/unit/babel_and_browserify_script_with_source_map.js
@@ -0,0 +1,79 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+"use strict";
+
+var add = require("./lib/add");
+var subtract = require("./lib/subtract");
+var upperCase = require("upper-case");
+
+(function () {
+ return add(1, 2);
+});
+
+},{"./lib/add":2,"./lib/subtract":3,"upper-case":4}],2:[function(require,module,exports){
+"use strict";
+
+module.exports = function (a, b) {
+ return a + b;
+};
+
+},{}],3:[function(require,module,exports){
+"use strict";
+
+module.exports = function (a, b) {
+ return a - b;
+};
+
+},{}],4:[function(require,module,exports){
+/**
+ * Special language-specific overrides.
+ *
+ * Source: ftp://ftp.unicode.org/Public/UCD/latest/ucd/SpecialCasing.txt
+ *
+ * @type {Object}
+ */
+var LANGUAGES = {
+ tr: {
+ regexp: /[\u0069]/g,
+ map: {
+ '\u0069': '\u0130'
+ }
+ },
+ az: {
+ regexp: /[\u0069]/g,
+ map: {
+ '\u0069': '\u0130'
+ }
+ },
+ lt: {
+ regexp: /[\u0069\u006A\u012F]\u0307|\u0069\u0307[\u0300\u0301\u0303]/g,
+ map: {
+ '\u0069\u0307': '\u0049',
+ '\u006A\u0307': '\u004A',
+ '\u012F\u0307': '\u012E',
+ '\u0069\u0307\u0300': '\u00CC',
+ '\u0069\u0307\u0301': '\u00CD',
+ '\u0069\u0307\u0303': '\u0128'
+ }
+ }
+}
+
+/**
+ * Upper case a string.
+ *
+ * @param {String} str
+ * @return {String}
+ */
+module.exports = function (str, locale) {
+ var lang = LANGUAGES[locale]
+
+ str = str == null ? '' : String(str)
+
+ if (lang) {
+ str = str.replace(lang.regexp, function (m) { return lang.map[m] })
+ }
+
+ return str.toUpperCase()
+}
+
+},{}]},{},[1])
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Vzci9sb2NhbC9saWIvbm9kZV9tb2R1bGVzL2Jyb3dzZXJpZnkvbm9kZV9tb2R1bGVzL2Jyb3dzZXItcGFjay9fcHJlbHVkZS5qcyIsIi9Vc2Vycy9qc2FudGVsbC9EZXYvc291cmNlLW1hcC10ZXN0L2luZGV4LmpzIiwiL1VzZXJzL2pzYW50ZWxsL0Rldi9zb3VyY2UtbWFwLXRlc3QvbGliL2FkZC5qcyIsIi9Vc2Vycy9qc2FudGVsbC9EZXYvc291cmNlLW1hcC10ZXN0L2xpYi9zdWJ0cmFjdC5qcyIsIm5vZGVfbW9kdWxlcy91cHBlci1jYXNlL3VwcGVyLWNhc2UuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztBQ0FBLElBQUksR0FBRyxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQztBQUMvQixJQUFJLFFBQVEsR0FBRyxPQUFPLENBQUMsZ0JBQWdCLENBQUMsQ0FBQztBQUN6QyxJQUFJLFNBQVMsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUM7O0FBRXRDLENBQUM7U0FBTSxHQUFHLENBQUMsQ0FBQyxFQUFDLENBQUMsQ0FBQztFQUFBLENBQUU7Ozs7O0FDSmpCLE1BQU0sQ0FBQyxPQUFPLEdBQUcsVUFBVSxDQUFDLEVBQUUsQ0FBQyxFQUFFO0FBQy9CLFNBQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQztDQUNkLENBQUM7Ozs7O0FDRkYsTUFBTSxDQUFDLE9BQU8sR0FBRyxVQUFVLENBQUMsRUFBRSxDQUFDLEVBQUU7QUFDL0IsU0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO0NBQ2QsQ0FBQzs7O0FDRkY7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsInZhciBhZGQgPSByZXF1aXJlKFwiLi9saWIvYWRkXCIpO1xudmFyIHN1YnRyYWN0ID0gcmVxdWlyZShcIi4vbGliL3N1YnRyYWN0XCIpO1xudmFyIHVwcGVyQ2FzZSA9IHJlcXVpcmUoXCJ1cHBlci1jYXNlXCIpO1xuXG4oKCkgPT4gYWRkKDEsMikpO1xuIiwibW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoYSwgYikge1xuICByZXR1cm4gYSArIGI7XG59O1xuIiwibW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAoYSwgYikge1xuICByZXR1cm4gYSAtIGI7XG59O1xuIiwiLyoqXG4gKiBTcGVjaWFsIGxhbmd1YWdlLXNwZWNpZmljIG92ZXJyaWRlcy5cbiAqXG4gKiBTb3VyY2U6IGZ0cDovL2Z0cC51bmljb2RlLm9yZy9QdWJsaWMvVUNEL2xhdGVzdC91Y2QvU3BlY2lhbENhc2luZy50eHRcbiAqXG4gKiBAdHlwZSB7T2JqZWN0fVxuICovXG52YXIgTEFOR1VBR0VTID0ge1xuICB0cjoge1xuICAgIHJlZ2V4cDogL1tcXHUwMDY5XS9nLFxuICAgIG1hcDoge1xuICAgICAgJ1xcdTAwNjknOiAnXFx1MDEzMCdcbiAgICB9XG4gIH0sXG4gIGF6OiB7XG4gICAgcmVnZXhwOiAvW1xcdTAwNjldL2csXG4gICAgbWFwOiB7XG4gICAgICAnXFx1MDA2OSc6ICdcXHUwMTMwJ1xuICAgIH1cbiAgfSxcbiAgbHQ6IHtcbiAgICByZWdleHA6IC9bXFx1MDA2OVxcdTAwNkFcXHUwMTJGXVxcdTAzMDd8XFx1MDA2OVxcdTAzMDdbXFx1MDMwMFxcdTAzMDFcXHUwMzAzXS9nLFxuICAgIG1hcDoge1xuICAgICAgJ1xcdTAwNjlcXHUwMzA3JzogJ1xcdTAwNDknLFxuICAgICAgJ1xcdTAwNkFcXHUwMzA3JzogJ1xcdTAwNEEnLFxuICAgICAgJ1xcdTAxMkZcXHUwMzA3JzogJ1xcdTAxMkUnLFxuICAgICAgJ1xcdTAwNjlcXHUwMzA3XFx1MDMwMCc6ICdcXHUwMENDJyxcbiAgICAgICdcXHUwMDY5XFx1MDMwN1xcdTAzMDEnOiAnXFx1MDBDRCcsXG4gICAgICAnXFx1MDA2OVxcdTAzMDdcXHUwMzAzJzogJ1xcdTAxMjgnXG4gICAgfVxuICB9XG59XG5cbi8qKlxuICogVXBwZXIgY2FzZSBhIHN0cmluZy5cbiAqXG4gKiBAcGFyYW0gIHtTdHJpbmd9IHN0clxuICogQHJldHVybiB7U3RyaW5nfVxuICovXG5tb2R1bGUuZXhwb3J0cyA9IGZ1bmN0aW9uIChzdHIsIGxvY2FsZSkge1xuICB2YXIgbGFuZyA9IExBTkdVQUdFU1tsb2NhbGVdXG5cbiAgc3RyID0gc3RyID09IG51bGwgPyAnJyA6IFN0cmluZyhzdHIpXG5cbiAgaWYgKGxhbmcpIHtcbiAgICBzdHIgPSBzdHIucmVwbGFjZShsYW5nLnJlZ2V4cCwgZnVuY3Rpb24gKG0pIHsgcmV0dXJuIGxhbmcubWFwW21dIH0pXG4gIH1cblxuICByZXR1cm4gc3RyLnRvVXBwZXJDYXNlKClcbn1cbiJdfQ==
diff --git a/devtools/server/tests/unit/head_dbg.js b/devtools/server/tests/unit/head_dbg.js
new file mode 100644
index 000000000..57d0eb8ff
--- /dev/null
+++ b/devtools/server/tests/unit/head_dbg.js
@@ -0,0 +1,862 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+var CC = Components.Constructor;
+
+// Populate AppInfo before anything (like the shared loader) accesses
+// System.appinfo, which is a lazy getter.
+const _appInfo = {};
+Cu.import("resource://testing-common/AppInfo.jsm", _appInfo);
+_appInfo.updateAppInfo({
+ ID: "devtools@tests.mozilla.org",
+ name: "devtools-tests",
+ version: "1",
+ platformVersion: "42",
+ crashReporter: true,
+});
+
+const { require, loader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { worker } = Cu.import("resource://devtools/shared/worker/loader.js", {});
+const promise = require("promise");
+const { Task } = require("devtools/shared/task");
+const { console } = require("resource://gre/modules/Console.jsm");
+const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
+
+const Services = require("Services");
+// Always log packets when running tests. runxpcshelltests.py will throw
+// the output away anyway, unless you give it the --verbose flag.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+// Enable remote debugging for the relevant tests.
+Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
+
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const { DebuggerServer } = require("devtools/server/main");
+const { DebuggerServer: WorkerDebuggerServer } = worker.require("devtools/server/main");
+const { DebuggerClient, ObjectClient } = require("devtools/shared/client/main");
+const { MemoryFront } = require("devtools/shared/fronts/memory");
+
+const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
+
+const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
+
+var loadSubScript = Cc[
+ "@mozilla.org/moz/jssubscript-loader;1"
+].getService(Ci.mozIJSSubScriptLoader).loadSubScript;
+
+/**
+ * Initializes any test that needs to work with add-ons.
+ */
+function startupAddonsManager() {
+ // Create a directory for extensions.
+ const profileDir = do_get_profile().clone();
+ profileDir.append("extensions");
+
+ const internalManager = Cc["@mozilla.org/addons/integration;1"]
+ .getService(Ci.nsIObserver)
+ .QueryInterface(Ci.nsITimerCallback);
+
+ internalManager.observe(null, "addons-startup", null);
+}
+
+/**
+ * Create a `run_test` function that runs the given generator in a task after
+ * having attached to a memory actor. When done, the memory actor is detached
+ * from, the client is finished, and the test is finished.
+ *
+ * @param {GeneratorFunction} testGeneratorFunction
+ * The generator function is passed (DebuggerClient, MemoryFront)
+ * arguments.
+ *
+ * @returns `run_test` function
+ */
+function makeMemoryActorTest(testGeneratorFunction) {
+ const TEST_GLOBAL_NAME = "test_MemoryActor";
+
+ return function run_test() {
+ do_test_pending();
+ startTestDebuggerServer(TEST_GLOBAL_NAME).then(client => {
+ DebuggerServer.registerModule("devtools/server/actors/heap-snapshot-file", {
+ prefix: "heapSnapshotFile",
+ constructor: "HeapSnapshotFileActor",
+ type: { global: true }
+ });
+
+ getTestTab(client, TEST_GLOBAL_NAME, function (tabForm, rootForm) {
+ if (!tabForm || !rootForm) {
+ ok(false, "Could not attach to test tab: " + TEST_GLOBAL_NAME);
+ return;
+ }
+
+ Task.spawn(function* () {
+ try {
+ const memoryFront = new MemoryFront(client, tabForm, rootForm);
+ yield memoryFront.attach();
+ yield* testGeneratorFunction(client, memoryFront);
+ yield memoryFront.detach();
+ } catch (err) {
+ DevToolsUtils.reportException("makeMemoryActorTest", err);
+ ok(false, "Got an error: " + err);
+ }
+
+ finishClient(client);
+ });
+ });
+ });
+ };
+}
+
+/**
+ * Save as makeMemoryActorTest but attaches the MemoryFront to the MemoryActor
+ * scoped to the full runtime rather than to a tab.
+ */
+function makeFullRuntimeMemoryActorTest(testGeneratorFunction) {
+ return function run_test() {
+ do_test_pending();
+ startTestDebuggerServer("test_MemoryActor").then(client => {
+ DebuggerServer.registerModule("devtools/server/actors/heap-snapshot-file", {
+ prefix: "heapSnapshotFile",
+ constructor: "HeapSnapshotFileActor",
+ type: { global: true }
+ });
+
+ getChromeActors(client).then(function (form) {
+ if (!form) {
+ ok(false, "Could not attach to chrome actors");
+ return;
+ }
+
+ Task.spawn(function* () {
+ try {
+ const rootForm = yield listTabs(client);
+ const memoryFront = new MemoryFront(client, form, rootForm);
+ yield memoryFront.attach();
+ yield* testGeneratorFunction(client, memoryFront);
+ yield memoryFront.detach();
+ } catch (err) {
+ DevToolsUtils.reportException("makeMemoryActorTest", err);
+ ok(false, "Got an error: " + err);
+ }
+
+ finishClient(client);
+ });
+ });
+ });
+ };
+}
+
+function createTestGlobal(name) {
+ let sandbox = Cu.Sandbox(
+ Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
+ );
+ sandbox.__name = name;
+ return sandbox;
+}
+
+function connect(client) {
+ dump("Connecting client.\n");
+ return client.connect();
+}
+
+function close(client) {
+ dump("Closing client.\n");
+ return client.close();
+}
+
+function listTabs(client) {
+ dump("Listing tabs.\n");
+ return client.listTabs();
+}
+
+function findTab(tabs, title) {
+ dump("Finding tab with title '" + title + "'.\n");
+ for (let tab of tabs) {
+ if (tab.title === title) {
+ return tab;
+ }
+ }
+ return null;
+}
+
+function attachTab(client, tab) {
+ dump("Attaching to tab with title '" + tab.title + "'.\n");
+ return client.attachTab(tab.actor);
+}
+
+function waitForNewSource(threadClient, url) {
+ dump("Waiting for new source with url '" + url + "'.\n");
+ return waitForEvent(threadClient, "newSource", function (packet) {
+ return packet.source.url === url;
+ });
+}
+
+function attachThread(tabClient, options = {}) {
+ dump("Attaching to thread.\n");
+ return tabClient.attachThread(options);
+}
+
+function resume(threadClient) {
+ dump("Resuming thread.\n");
+ return threadClient.resume();
+}
+
+function getSources(threadClient) {
+ dump("Getting sources.\n");
+ return threadClient.getSources();
+}
+
+function findSource(sources, url) {
+ dump("Finding source with url '" + url + "'.\n");
+ for (let source of sources) {
+ if (source.url === url) {
+ return source;
+ }
+ }
+ return null;
+}
+
+function waitForPause(threadClient) {
+ dump("Waiting for pause.\n");
+ return waitForEvent(threadClient, "paused");
+}
+
+function setBreakpoint(sourceClient, location) {
+ dump("Setting breakpoint.\n");
+ return sourceClient.setBreakpoint(location);
+}
+
+function dumpn(msg) {
+ dump("DBG-TEST: " + msg + "\n");
+}
+
+function testExceptionHook(ex) {
+ try {
+ do_report_unexpected_exception(ex);
+ } catch (ex) {
+ return {throw: ex};
+ }
+ return undefined;
+}
+
+// Convert an nsIScriptError 'aFlags' value into an appropriate string.
+function scriptErrorFlagsToKind(aFlags) {
+ var kind;
+ if (aFlags & Ci.nsIScriptError.warningFlag)
+ kind = "warning";
+ if (aFlags & Ci.nsIScriptError.exceptionFlag)
+ kind = "exception";
+ else
+ kind = "error";
+
+ if (aFlags & Ci.nsIScriptError.strictFlag)
+ kind = "strict " + kind;
+
+ return kind;
+}
+
+// Register a console listener, so console messages don't just disappear
+// into the ether.
+var errorCount = 0;
+var listener = {
+ observe: function (aMessage) {
+ try {
+ errorCount++;
+ try {
+ // If we've been given an nsIScriptError, then we can print out
+ // something nicely formatted, for tools like Emacs to pick up.
+ var scriptError = aMessage.QueryInterface(Ci.nsIScriptError);
+ dumpn(aMessage.sourceName + ":" + aMessage.lineNumber + ": " +
+ scriptErrorFlagsToKind(aMessage.flags) + ": " +
+ aMessage.errorMessage);
+ var string = aMessage.errorMessage;
+ } catch (x) {
+ // Be a little paranoid with message, as the whole goal here is to lose
+ // no information.
+ try {
+ var string = "" + aMessage.message;
+ } catch (x) {
+ var string = "<error converting error message to string>";
+ }
+ }
+
+ // Make sure we exit all nested event loops so that the test can finish.
+ while (DebuggerServer
+ && DebuggerServer.xpcInspector
+ && DebuggerServer.xpcInspector.eventLoopNestLevel > 0) {
+ DebuggerServer.xpcInspector.exitNestedEventLoop();
+ }
+
+ // In the world before bug 997440, exceptions were getting lost because of
+ // the arbitrary JSContext being used in nsXPCWrappedJSClass::CallMethod.
+ // In the new world, the wanderers have returned. However, because of the,
+ // currently very-broken, exception reporting machinery in
+ // XPCWrappedJSClass these get reported as errors to the console, even if
+ // there's actually JS on the stack above that will catch them. If we
+ // throw an error here because of them our tests start failing. So, we'll
+ // just dump the message to the logs instead, to make sure the information
+ // isn't lost.
+ dumpn("head_dbg.js observed a console message: " + string);
+ } catch (_) {
+ // Swallow everything to avoid console reentrancy errors. We did our best
+ // to log above, but apparently that didn't cut it.
+ }
+ }
+};
+
+var consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
+consoleService.registerListener(listener);
+
+function check_except(func)
+{
+ try {
+ func();
+ } catch (e) {
+ do_check_true(true);
+ return;
+ }
+ dumpn("Should have thrown an exception: " + func.toString());
+ do_check_true(false);
+}
+
+function testGlobal(aName) {
+ let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
+ .createInstance(Ci.nsIPrincipal);
+
+ let sandbox = Cu.Sandbox(systemPrincipal);
+ sandbox.__name = aName;
+ return sandbox;
+}
+
+function addTestGlobal(aName, aServer = DebuggerServer)
+{
+ let global = testGlobal(aName);
+ aServer.addTestGlobal(global);
+ return global;
+}
+
+// List the DebuggerClient |aClient|'s tabs, look for one whose title is
+// |aTitle|, and apply |aCallback| to the packet's entry for that tab.
+function getTestTab(aClient, aTitle, aCallback) {
+ aClient.listTabs(function (aResponse) {
+ for (let tab of aResponse.tabs) {
+ if (tab.title === aTitle) {
+ aCallback(tab, aResponse);
+ return;
+ }
+ }
+ aCallback(null);
+ });
+}
+
+// Attach to |aClient|'s tab whose title is |aTitle|; pass |aCallback| the
+// response packet and a TabClient instance referring to that tab.
+function attachTestTab(aClient, aTitle, aCallback) {
+ getTestTab(aClient, aTitle, function (aTab) {
+ aClient.attachTab(aTab.actor, aCallback);
+ });
+}
+
+// Attach to |aClient|'s tab whose title is |aTitle|, and then attach to
+// that tab's thread. Pass |aCallback| the thread attach response packet, a
+// TabClient referring to the tab, and a ThreadClient referring to the
+// thread.
+function attachTestThread(aClient, aTitle, aCallback) {
+ attachTestTab(aClient, aTitle, function (aTabResponse, aTabClient) {
+ function onAttach(aResponse, aThreadClient) {
+ aCallback(aResponse, aTabClient, aThreadClient, aTabResponse);
+ }
+ aTabClient.attachThread({
+ useSourceMaps: true,
+ autoBlackBox: true
+ }, onAttach);
+ });
+}
+
+// Attach to |aClient|'s tab whose title is |aTitle|, attach to the tab's
+// thread, and then resume it. Pass |aCallback| the thread's response to
+// the 'resume' packet, a TabClient for the tab, and a ThreadClient for the
+// thread.
+function attachTestTabAndResume(aClient, aTitle, aCallback = () => {}) {
+ return new Promise((resolve, reject) => {
+ attachTestThread(aClient, aTitle, function (aResponse, aTabClient, aThreadClient) {
+ aThreadClient.resume(function (aResponse) {
+ aCallback(aResponse, aTabClient, aThreadClient);
+ resolve([aResponse, aTabClient, aThreadClient]);
+ });
+ });
+ });
+}
+
+/**
+ * Initialize the testing debugger server.
+ */
+function initTestDebuggerServer(aServer = DebuggerServer)
+{
+ aServer.registerModule("xpcshell-test/testactors");
+ // Allow incoming connections.
+ aServer.init(function () { return true; });
+}
+
+/**
+ * Initialize the testing debugger server with a tab whose title is |title|.
+ */
+function startTestDebuggerServer(title, server = DebuggerServer) {
+ initTestDebuggerServer(server);
+ addTestGlobal(title);
+ DebuggerServer.addTabActors();
+
+ let transport = DebuggerServer.connectPipe();
+ let client = new DebuggerClient(transport);
+
+ return connect(client).then(() => client);
+}
+
+function finishClient(aClient)
+{
+ aClient.close(function () {
+ DebuggerServer.destroy();
+ do_test_finished();
+ });
+}
+
+// Create a server, connect to it and fetch tab actors for the parent process;
+// pass |aCallback| the debugger client and tab actor form with all actor IDs.
+function get_chrome_actors(callback)
+{
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+ DebuggerServer.allowChromeProcess = true;
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect()
+ .then(() => client.getProcess())
+ .then(response => {
+ callback(client, response.form);
+ });
+}
+
+function getChromeActors(client, server = DebuggerServer) {
+ server.allowChromeProcess = true;
+ return client.getProcess().then(response => response.form);
+}
+
+/**
+ * Takes a relative file path and returns the absolute file url for it.
+ */
+function getFileUrl(aName, aAllowMissing = false) {
+ let file = do_get_file(aName, aAllowMissing);
+ return Services.io.newFileURI(file).spec;
+}
+
+/**
+ * Returns the full path of the file with the specified name in a
+ * platform-independent and URL-like form.
+ */
+function getFilePath(aName, aAllowMissing = false, aUsePlatformPathSeparator = false)
+{
+ let file = do_get_file(aName, aAllowMissing);
+ let path = Services.io.newFileURI(file).spec;
+ let filePrePath = "file://";
+ if ("nsILocalFileWin" in Ci &&
+ file instanceof Ci.nsILocalFileWin) {
+ filePrePath += "/";
+ }
+
+ path = path.slice(filePrePath.length);
+
+ if (aUsePlatformPathSeparator && path.match(/^\w:/)) {
+ path = path.replace(/\//g, "\\");
+ }
+
+ return path;
+}
+
+/**
+ * Returns the full text contents of the given file.
+ */
+function readFile(aFileName) {
+ let f = do_get_file(aFileName);
+ let s = Cc["@mozilla.org/network/file-input-stream;1"]
+ .createInstance(Ci.nsIFileInputStream);
+ s.init(f, -1, -1, false);
+ try {
+ return NetUtil.readInputStreamToString(s, s.available());
+ } finally {
+ s.close();
+ }
+}
+
+function writeFile(aFileName, aContent) {
+ let file = do_get_file(aFileName, true);
+ let stream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ stream.init(file, -1, -1, 0);
+ try {
+ do {
+ let numWritten = stream.write(aContent, aContent.length);
+ aContent = aContent.slice(numWritten);
+ } while (aContent.length > 0);
+ } finally {
+ stream.close();
+ }
+}
+
+function connectPipeTracing() {
+ return new TracingTransport(DebuggerServer.connectPipe());
+}
+
+function TracingTransport(childTransport) {
+ this.hooks = null;
+ this.child = childTransport;
+ this.child.hooks = this;
+
+ this.expectations = [];
+ this.packets = [];
+ this.checkIndex = 0;
+}
+
+TracingTransport.prototype = {
+ // Remove actor names
+ normalize: function (packet) {
+ return JSON.parse(JSON.stringify(packet, (key, value) => {
+ if (key === "to" || key === "from" || key === "actor") {
+ return "<actorid>";
+ }
+ return value;
+ }));
+ },
+ send: function (packet) {
+ this.packets.push({
+ type: "sent",
+ packet: this.normalize(packet)
+ });
+ return this.child.send(packet);
+ },
+ close: function () {
+ return this.child.close();
+ },
+ ready: function () {
+ return this.child.ready();
+ },
+ onPacket: function (packet) {
+ this.packets.push({
+ type: "received",
+ packet: this.normalize(packet)
+ });
+ this.hooks.onPacket(packet);
+ },
+ onClosed: function () {
+ this.hooks.onClosed();
+ },
+
+ expectSend: function (expected) {
+ let packet = this.packets[this.checkIndex++];
+ do_check_eq(packet.type, "sent");
+ deepEqual(packet.packet, this.normalize(expected));
+ },
+
+ expectReceive: function (expected) {
+ let packet = this.packets[this.checkIndex++];
+ do_check_eq(packet.type, "received");
+ deepEqual(packet.packet, this.normalize(expected));
+ },
+
+ // Write your tests, call dumpLog at the end, inspect the output,
+ // then sprinkle the calls through the right places in your test.
+ dumpLog: function () {
+ for (let entry of this.packets) {
+ if (entry.type === "sent") {
+ dumpn("trace.expectSend(" + entry.packet + ");");
+ } else {
+ dumpn("trace.expectReceive(" + entry.packet + ");");
+ }
+ }
+ }
+};
+
+function StubTransport() { }
+StubTransport.prototype.ready = function () {};
+StubTransport.prototype.send = function () {};
+StubTransport.prototype.close = function () {};
+
+function executeSoon(aFunc) {
+ Services.tm.mainThread.dispatch({
+ run: DevToolsUtils.makeInfallible(aFunc)
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+}
+
+// The do_check_* family of functions expect their last argument to be an
+// optional stack object. Unfortunately, most tests actually pass a in a string
+// containing an error message instead, which causes error reporting to break if
+// strict warnings as errors is turned on. To avoid this, we wrap these
+// functions here below to ensure the correct number of arguments is passed.
+//
+// TODO: Remove this once bug 906232 is resolved
+//
+var do_check_true_old = do_check_true;
+var do_check_true = function (condition) {
+ do_check_true_old(condition);
+};
+
+var do_check_false_old = do_check_false;
+var do_check_false = function (condition) {
+ do_check_false_old(condition);
+};
+
+var do_check_eq_old = do_check_eq;
+var do_check_eq = function (left, right) {
+ do_check_eq_old(left, right);
+};
+
+var do_check_neq_old = do_check_neq;
+var do_check_neq = function (left, right) {
+ do_check_neq_old(left, right);
+};
+
+var do_check_matches_old = do_check_matches;
+var do_check_matches = function (pattern, value) {
+ do_check_matches_old(pattern, value);
+};
+
+// Create async version of the object where calling each method
+// is equivalent of calling it with asyncall. Mainly useful for
+// destructuring objects with methods that take callbacks.
+const Async = target => new Proxy(target, Async);
+Async.get = (target, name) =>
+ typeof (target[name]) === "function" ? asyncall.bind(null, target[name], target) :
+ target[name];
+
+// Calls async function that takes callback and errorback and returns
+// returns promise representing result.
+const asyncall = (fn, self, ...args) =>
+ new Promise((...etc) => fn.call(self, ...args, ...etc));
+
+const Test = task => () => {
+ add_task(task);
+ run_next_test();
+};
+
+const assert = do_check_true;
+
+/**
+ * Create a promise that is resolved on the next occurence of the given event.
+ *
+ * @param DebuggerClient client
+ * @param String event
+ * @param Function predicate
+ * @returns Promise
+ */
+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);
+ });
+ }
+ });
+}
+
+/**
+ * Execute the action on the next tick and return a promise that is resolved on
+ * the next pause.
+ *
+ * When using promises and Task.jsm, we often want to do an action that causes a
+ * pause and continue the task once the pause has ocurred. Unfortunately, if we
+ * do the action that causes the pause within the task's current tick we will
+ * pause before we have a chance to yield the promise that waits for the pause
+ * and we enter a dead lock. The solution is to create the promise that waits
+ * for the pause, schedule the action to run on the next tick of the event loop,
+ * and finally yield the promise.
+ *
+ * @param Function action
+ * @param DebuggerClient client
+ * @returns Promise
+ */
+function executeOnNextTickAndWaitForPause(action, client) {
+ const paused = waitForPause(client);
+ executeSoon(action);
+ return paused;
+}
+
+/**
+ * Interrupt JS execution for the specified thread.
+ *
+ * @param ThreadClient threadClient
+ * @returns Promise
+ */
+function interrupt(threadClient) {
+ dumpn("Interrupting.");
+ return threadClient.interrupt();
+}
+
+/**
+ * Resume JS execution for the specified thread and then wait for the next pause
+ * event.
+ *
+ * @param DebuggerClient client
+ * @param ThreadClient threadClient
+ * @returns Promise
+ */
+function resumeAndWaitForPause(client, threadClient) {
+ const paused = waitForPause(client);
+ return resume(threadClient).then(() => paused);
+}
+
+/**
+ * Resume JS execution for a single step and wait for the pause after the step
+ * has been taken.
+ *
+ * @param DebuggerClient client
+ * @param ThreadClient threadClient
+ * @returns Promise
+ */
+function stepIn(client, threadClient) {
+ dumpn("Stepping in.");
+ const paused = waitForPause(client);
+ return threadClient.stepIn()
+ .then(() => paused);
+}
+
+/**
+ * Resume JS execution for a step over and wait for the pause after the step
+ * has been taken.
+ *
+ * @param DebuggerClient client
+ * @param ThreadClient threadClient
+ * @returns Promise
+ */
+function stepOver(client, threadClient) {
+ dumpn("Stepping over.");
+ return threadClient.stepOver()
+ .then(() => waitForPause(client));
+}
+
+/**
+ * Get the list of `count` frames currently on stack, starting at the index
+ * `first` for the specified thread.
+ *
+ * @param ThreadClient threadClient
+ * @param Number first
+ * @param Number count
+ * @returns Promise
+ */
+function getFrames(threadClient, first, count) {
+ dumpn("Getting frames.");
+ return threadClient.getFrames(first, count);
+}
+
+/**
+ * Black box the specified source.
+ *
+ * @param SourceClient sourceClient
+ * @returns Promise
+ */
+function blackBox(sourceClient) {
+ dumpn("Black boxing source: " + sourceClient.actor);
+ return sourceClient.blackBox();
+}
+
+/**
+ * Stop black boxing the specified source.
+ *
+ * @param SourceClient sourceClient
+ * @returns Promise
+ */
+function unBlackBox(sourceClient) {
+ dumpn("Un-black boxing source: " + sourceClient.actor);
+ return sourceClient.unblackBox();
+}
+
+/**
+ * Perform a "source" RDP request with the given SourceClient to get the source
+ * content and content type.
+ *
+ * @param SourceClient sourceClient
+ * @returns Promise
+ */
+function getSourceContent(sourceClient) {
+ dumpn("Getting source content for " + sourceClient.actor);
+ return sourceClient.source();
+}
+
+/**
+ * Get a source at the specified url.
+ *
+ * @param ThreadClient threadClient
+ * @param string url
+ * @returns Promise<SourceClient>
+ */
+function getSource(threadClient, url) {
+ let deferred = promise.defer();
+ threadClient.getSources((res) => {
+ let source = res.sources.filter(function (s) {
+ return s.url === url;
+ });
+ if (source.length) {
+ deferred.resolve(threadClient.source(source[0]));
+ }
+ else {
+ deferred.reject(new Error("source not found"));
+ }
+ });
+ return deferred.promise;
+}
+
+/**
+ * Do a fake reload which clears the thread debugger
+ *
+ * @param TabClient tabClient
+ * @returns Promise<response>
+ */
+function reload(tabClient) {
+ let deferred = promise.defer();
+ tabClient._reload({}, deferred.resolve);
+ return deferred.promise;
+}
+
+/**
+ * Returns an array of stack location strings given a thread and a sample.
+ *
+ * @param object thread
+ * @param object sample
+ * @returns object
+ */
+function getInflatedStackLocations(thread, sample) {
+ let stackTable = thread.stackTable;
+ let frameTable = thread.frameTable;
+ let stringTable = thread.stringTable;
+ let SAMPLE_STACK_SLOT = thread.samples.schema.stack;
+ let STACK_PREFIX_SLOT = stackTable.schema.prefix;
+ let STACK_FRAME_SLOT = stackTable.schema.frame;
+ let FRAME_LOCATION_SLOT = frameTable.schema.location;
+
+ // Build the stack from the raw data and accumulate the locations in
+ // an array.
+ let stackIndex = sample[SAMPLE_STACK_SLOT];
+ let locations = [];
+ while (stackIndex !== null) {
+ let stackEntry = stackTable.data[stackIndex];
+ let frame = frameTable.data[stackEntry[STACK_FRAME_SLOT]];
+ locations.push(stringTable[frame[FRAME_LOCATION_SLOT]]);
+ stackIndex = stackEntry[STACK_PREFIX_SLOT];
+ }
+
+ // The profiler tree is inverted, so reverse the array.
+ return locations.reverse();
+}
diff --git a/devtools/server/tests/unit/hello-actor.js b/devtools/server/tests/unit/hello-actor.js
new file mode 100644
index 000000000..6d7427f63
--- /dev/null
+++ b/devtools/server/tests/unit/hello-actor.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const protocol = require("devtools/shared/protocol");
+
+const helloSpec = protocol.generateActorSpec({
+ typeName: "helloActor",
+
+ methods: {
+ hello: {}
+ }
+});
+
+var HelloActor = protocol.ActorClassWithSpec(helloSpec, {
+ hello: function () {
+ return;
+ }
+});
diff --git a/devtools/server/tests/unit/post_init_global_actors.js b/devtools/server/tests/unit/post_init_global_actors.js
new file mode 100644
index 000000000..0035e8914
--- /dev/null
+++ b/devtools/server/tests/unit/post_init_global_actors.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function PostInitGlobalActor(aConnection) {}
+
+PostInitGlobalActor.prototype = {
+ actorPrefix: "postInitGlobal",
+ onPing: function onPing(aRequest) {
+ return { message: "pong" };
+ },
+};
+
+PostInitGlobalActor.prototype.requestTypes = {
+ "ping": PostInitGlobalActor.prototype.onPing,
+};
+
+DebuggerServer.addGlobalActor(PostInitGlobalActor, "postInitGlobalActor");
diff --git a/devtools/server/tests/unit/post_init_tab_actors.js b/devtools/server/tests/unit/post_init_tab_actors.js
new file mode 100644
index 000000000..9b9ddf111
--- /dev/null
+++ b/devtools/server/tests/unit/post_init_tab_actors.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function PostInitTabActor(aConnection) {}
+
+PostInitTabActor.prototype = {
+ actorPostfix: "postInitTab",
+ onPing: function onPing(aRequest) {
+ return { message: "pong" };
+ },
+};
+
+PostInitTabActor.prototype.requestTypes = {
+ "ping": PostInitTabActor.prototype.onPing,
+};
+
+DebuggerServer.addGlobalActor(PostInitTabActor, "postInitTabActor");
diff --git a/devtools/server/tests/unit/pre_init_global_actors.js b/devtools/server/tests/unit/pre_init_global_actors.js
new file mode 100644
index 000000000..bd4284a70
--- /dev/null
+++ b/devtools/server/tests/unit/pre_init_global_actors.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function PreInitGlobalActor(aConnection) {}
+
+PreInitGlobalActor.prototype = {
+ actorPrefix: "preInitGlobal",
+ onPing: function onPing(aRequest) {
+ return { message: "pong" };
+ },
+};
+
+PreInitGlobalActor.prototype.requestTypes = {
+ "ping": PreInitGlobalActor.prototype.onPing,
+};
+
+DebuggerServer.addGlobalActor(PreInitGlobalActor, "preInitGlobalActor");
diff --git a/devtools/server/tests/unit/pre_init_tab_actors.js b/devtools/server/tests/unit/pre_init_tab_actors.js
new file mode 100644
index 000000000..628f0fb2f
--- /dev/null
+++ b/devtools/server/tests/unit/pre_init_tab_actors.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function PreInitTabActor(aConnection) {}
+
+PreInitTabActor.prototype = {
+ actorPrefix: "preInitTab",
+ onPing: function onPing(aRequest) {
+ return { message: "pong" };
+ },
+};
+
+PreInitTabActor.prototype.requestTypes = {
+ "ping": PreInitTabActor.prototype.onPing,
+};
+
+DebuggerServer.addGlobalActor(PreInitTabActor, "preInitTabActor");
diff --git a/devtools/server/tests/unit/registertestactors-01.js b/devtools/server/tests/unit/registertestactors-01.js
new file mode 100644
index 000000000..92f511225
--- /dev/null
+++ b/devtools/server/tests/unit/registertestactors-01.js
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function Actor() {}
+
+exports.register = function (handle) {
+ handle.addTabActor(Actor, "registeredActor1");
+ handle.addGlobalActor(Actor, "registeredActor1");
+};
+
+exports.unregister = function (handle) {
+ handle.removeTabActor(Actor);
+ handle.removeGlobalActor(Actor);
+};
+
diff --git a/devtools/server/tests/unit/registertestactors-02.js b/devtools/server/tests/unit/registertestactors-02.js
new file mode 100644
index 000000000..54f78e508
--- /dev/null
+++ b/devtools/server/tests/unit/registertestactors-02.js
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function Actor() {}
+
+exports.register = function (handle) {
+ handle.addGlobalActor(Actor, "registeredActor2");
+ handle.addTabActor(Actor, "registeredActor2");
+};
+
+exports.unregister = function (handle) {
+ handle.removeTabActor(Actor);
+ handle.removeGlobalActor(Actor);
+};
+
diff --git a/devtools/server/tests/unit/registertestactors-03.js b/devtools/server/tests/unit/registertestactors-03.js
new file mode 100644
index 000000000..8d42fdbd8
--- /dev/null
+++ b/devtools/server/tests/unit/registertestactors-03.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var {method, RetVal, Actor, ActorClassWithSpec, Front, FrontClassWithSpec,
+ generateActorSpec} = require("devtools/shared/protocol");
+var Services = require("Services");
+
+const lazySpec = generateActorSpec({
+ typeName: "lazy",
+
+ methods: {
+ hello: {
+ response: { str: RetVal("string") }
+ }
+ }
+});
+
+exports.LazyActor = ActorClassWithSpec(lazySpec, {
+ initialize: function (conn, id) {
+ Actor.prototype.initialize.call(this, conn);
+
+ Services.obs.notifyObservers(null, "actor", "instantiated");
+ },
+
+ hello: function (str) {
+ return "world";
+ }
+});
+
+Services.obs.notifyObservers(null, "actor", "loaded");
+
+exports.LazyFront = FrontClassWithSpec(lazySpec, {
+ initialize: function (client, form) {
+ Front.prototype.initialize.call(this, client);
+ this.actorID = form.lazyActor;
+
+ client.addActorPool(this);
+ this.manage(this);
+ }
+});
diff --git a/devtools/server/tests/unit/setBreakpoint-on-column-in-gcd-script.js b/devtools/server/tests/unit/setBreakpoint-on-column-in-gcd-script.js
new file mode 100644
index 000000000..575915c4f
--- /dev/null
+++ b/devtools/server/tests/unit/setBreakpoint-on-column-in-gcd-script.js
@@ -0,0 +1,7 @@
+"use strict";
+
+function f() {}
+
+(function () {
+ var a = 1; var b = 2; var c = 3;
+})();
diff --git a/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-at-end-of-line.js b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-at-end-of-line.js
new file mode 100644
index 000000000..4c1b52eb4
--- /dev/null
+++ b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-at-end-of-line.js
@@ -0,0 +1,6 @@
+"use strict";
+
+function f() {
+ var a = 1; var b = 2;
+ var c = 3;
+}
diff --git a/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-in-gcd-script.js b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-in-gcd-script.js
new file mode 100644
index 000000000..adce39193
--- /dev/null
+++ b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets-in-gcd-script.js
@@ -0,0 +1,7 @@
+"use strict";
+
+function f() {}
+
+(function () {
+ var a = 1; var c = 3;
+})();
diff --git a/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets.js b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets.js
new file mode 100644
index 000000000..5faefc3c8
--- /dev/null
+++ b/devtools/server/tests/unit/setBreakpoint-on-column-with-no-offsets.js
@@ -0,0 +1,5 @@
+"use strict";
+
+function f() {
+ var a = 1; var c = 3;
+}
diff --git a/devtools/server/tests/unit/setBreakpoint-on-column.js b/devtools/server/tests/unit/setBreakpoint-on-column.js
new file mode 100644
index 000000000..d92231e65
--- /dev/null
+++ b/devtools/server/tests/unit/setBreakpoint-on-column.js
@@ -0,0 +1,5 @@
+"use strict";
+
+function f() {
+ var a = 1; var b = 2; var c = 3;
+}
diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-in-gcd-script.js b/devtools/server/tests/unit/setBreakpoint-on-line-in-gcd-script.js
new file mode 100644
index 000000000..fb96be8ab
--- /dev/null
+++ b/devtools/server/tests/unit/setBreakpoint-on-line-in-gcd-script.js
@@ -0,0 +1,9 @@
+"use strict";
+
+function f() {}
+
+(function () {
+ var a = 1;
+ var b = 2;
+ var c = 3;
+})();
diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-offsets.js b/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-offsets.js
new file mode 100644
index 000000000..b30ebb504
--- /dev/null
+++ b/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-offsets.js
@@ -0,0 +1,7 @@
+"use strict";
+
+function f() {
+ for (var i = 0; i < 1; ++i) {
+ ;
+ }
+}
diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-statements.js b/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-statements.js
new file mode 100644
index 000000000..d92231e65
--- /dev/null
+++ b/devtools/server/tests/unit/setBreakpoint-on-line-with-multiple-statements.js
@@ -0,0 +1,5 @@
+"use strict";
+
+function f() {
+ var a = 1; var b = 2; var c = 3;
+}
diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets-in-gcd-script.js b/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets-in-gcd-script.js
new file mode 100644
index 000000000..b03d40079
--- /dev/null
+++ b/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets-in-gcd-script.js
@@ -0,0 +1,9 @@
+"use strict";
+
+function f() {}
+
+(function () {
+ var a = 1;
+
+ var c = 3;
+})();
diff --git a/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets.js b/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets.js
new file mode 100644
index 000000000..1268cf8db
--- /dev/null
+++ b/devtools/server/tests/unit/setBreakpoint-on-line-with-no-offsets.js
@@ -0,0 +1,7 @@
+"use strict";
+
+function f() {
+ var a = 1;
+
+ var c = 3;
+}
diff --git a/devtools/server/tests/unit/setBreakpoint-on-line.js b/devtools/server/tests/unit/setBreakpoint-on-line.js
new file mode 100644
index 000000000..1b15e2a5e
--- /dev/null
+++ b/devtools/server/tests/unit/setBreakpoint-on-line.js
@@ -0,0 +1,7 @@
+"use strict";
+
+function f() {
+ var a = 1;
+ var b = 2;
+ var c = 3;
+}
diff --git a/devtools/server/tests/unit/source-map-data/sourcemapped.coffee b/devtools/server/tests/unit/source-map-data/sourcemapped.coffee
new file mode 100644
index 000000000..73a400a21
--- /dev/null
+++ b/devtools/server/tests/unit/source-map-data/sourcemapped.coffee
@@ -0,0 +1,6 @@
+foo = (n) ->
+ return "foo" + i for i in [0...n]
+
+[first, second, third] = foo(3)
+
+debugger \ No newline at end of file
diff --git a/devtools/server/tests/unit/source-map-data/sourcemapped.map b/devtools/server/tests/unit/source-map-data/sourcemapped.map
new file mode 100644
index 000000000..dcee3c33c
--- /dev/null
+++ b/devtools/server/tests/unit/source-map-data/sourcemapped.map
@@ -0,0 +1,10 @@
+{
+ "version": 3,
+ "file": "sourcemapped.js",
+ "sourceRoot": "",
+ "sources": [
+ "sourcemapped.coffee"
+ ],
+ "names": [],
+ "mappings": ";AAAA;CAAA,KAAA,yBAAA;CAAA;CAAA,CAAA,CAAA,MAAO;CACL,IAAA,GAAA;AAAA,CAAA,EAAA,MAA0B,qDAA1B;CAAA,EAAe,EAAR,QAAA;CAAP,IADI;CAAN,EAAM;;CAAN,CAGA,CAAyB,IAAA;;CAEzB,UALA;CAAA"
+} \ No newline at end of file
diff --git a/devtools/server/tests/unit/sourcemapped.js b/devtools/server/tests/unit/sourcemapped.js
new file mode 100644
index 000000000..94d130903
--- /dev/null
+++ b/devtools/server/tests/unit/sourcemapped.js
@@ -0,0 +1,16 @@
+// Generated by CoffeeScript 1.6.1
+(function () {
+ var first, foo, second, third, _ref;
+
+ foo = function (n) {
+ var i, _i;
+ for (i = _i = 0; 0 <= n ? _i < n : _i > n; i = 0 <= n ? ++_i : --_i) {
+ return "foo" + i;
+ }
+ };
+
+ _ref = foo(3), first = _ref[0], second = _ref[1], third = _ref[2];
+
+ debugger;
+
+}).call(this);
diff --git a/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_01.js b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_01.js
new file mode 100644
index 000000000..2fd38d49c
--- /dev/null
+++ b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_01.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that we can tell the memory actor to take a heap snapshot over the RDP
+// and then create a HeapSnapshot instance from the resulting file.
+
+const { OS } = require("resource://gre/modules/osfile.jsm");
+
+const run_test = makeMemoryActorTest(function* (client, memoryFront) {
+ const snapshotFilePath = yield memoryFront.saveHeapSnapshot();
+ ok(!!(yield OS.File.stat(snapshotFilePath)),
+ "Should have the heap snapshot file");
+ const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath);
+ ok(snapshot instanceof HeapSnapshot,
+ "And we should be able to read a HeapSnapshot instance from the file");
+});
diff --git a/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_02.js b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_02.js
new file mode 100644
index 000000000..564ec0d06
--- /dev/null
+++ b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_02.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that we can properly stream heap snapshot files over the RDP as bulk
+// data.
+
+const { OS } = require("resource://gre/modules/osfile.jsm");
+
+const run_test = makeMemoryActorTest(function* (client, memoryFront) {
+ const snapshotFilePath = yield memoryFront.saveHeapSnapshot({
+ forceCopy: true
+ });
+ ok(!!(yield OS.File.stat(snapshotFilePath)),
+ "Should have the heap snapshot file");
+ const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath);
+ ok(snapshot instanceof HeapSnapshot,
+ "And we should be able to read a HeapSnapshot instance from the file");
+});
diff --git a/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_03.js b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_03.js
new file mode 100644
index 000000000..e9e81594d
--- /dev/null
+++ b/devtools/server/tests/unit/test_MemoryActor_saveHeapSnapshot_03.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that we can save full runtime heap snapshots when attached to the
+// ChromeActor or a ChildProcessActor.
+
+const { OS } = require("resource://gre/modules/osfile.jsm");
+
+const run_test = makeFullRuntimeMemoryActorTest(function* (client, memoryFront) {
+ const snapshotFilePath = yield memoryFront.saveHeapSnapshot();
+ ok(!!(yield OS.File.stat(snapshotFilePath)),
+ "Should have the heap snapshot file");
+ const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath);
+ ok(snapshot instanceof HeapSnapshot,
+ "And we should be able to read a HeapSnapshot instance from the file");
+});
diff --git a/devtools/server/tests/unit/test_actor-registry-actor.js b/devtools/server/tests/unit/test_actor-registry-actor.js
new file mode 100644
index 000000000..8b0abfbbb
--- /dev/null
+++ b/devtools/server/tests/unit/test_actor-registry-actor.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that you can register new actors via the ActorRegistrationActor.
+ */
+
+var gClient;
+var gRegistryFront;
+var gActorFront;
+var gOldPref;
+
+const { ActorRegistryFront } = require("devtools/shared/fronts/actor-registry");
+
+function run_test()
+{
+ gOldPref = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps");
+ Services.prefs.setBoolPref("devtools.debugger.forbid-certified-apps", false);
+ initTestDebuggerServer();
+ DebuggerServer.addBrowserActors();
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(getRegistry);
+ do_test_pending();
+}
+
+function getRegistry() {
+ gClient.listTabs((response) => {
+ gRegistryFront = ActorRegistryFront(gClient, response);
+ registerNewActor();
+ });
+}
+
+function registerNewActor() {
+ let options = {
+ prefix: "helloActor",
+ constructor: "HelloActor",
+ type: { global: true }
+ };
+
+ gRegistryFront
+ .registerActor("resource://test/hello-actor.js", options)
+ .then(actorFront => gActorFront = actorFront)
+ .then(talkToNewActor)
+ .then(null, e => {
+ DevToolsUtils.reportException("registerNewActor", e);
+ do_check_true(false);
+ });
+}
+
+function talkToNewActor() {
+ gClient.listTabs(({ helloActor }) => {
+ do_check_true(!!helloActor);
+ gClient.request({
+ to: helloActor,
+ type: "hello"
+ }, response => {
+ do_check_true(!response.error);
+ unregisterNewActor();
+ });
+ });
+}
+
+function unregisterNewActor() {
+ gActorFront
+ .unregister()
+ .then(testActorIsUnregistered)
+ .then(null, e => {
+ DevToolsUtils.reportException("unregisterNewActor", e);
+ do_check_true(false);
+ });
+}
+
+function testActorIsUnregistered() {
+ gClient.listTabs(({ helloActor }) => {
+ do_check_true(!helloActor);
+
+ Services.prefs.setBoolPref("devtools.debugger.forbid-certified-apps", gOldPref);
+ finishClient(gClient);
+ });
+}
diff --git a/devtools/server/tests/unit/test_add_actors.js b/devtools/server/tests/unit/test_add_actors.js
new file mode 100644
index 000000000..9b90da724
--- /dev/null
+++ b/devtools/server/tests/unit/test_add_actors.js
@@ -0,0 +1,107 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gClient;
+var gActors;
+
+/**
+ * The purpose of these tests is to verify that it's possible to add actors
+ * both before and after the DebuggerServer has been initialized, so addons
+ * that add actors don't have to poll the object for its initialization state
+ * in order to add actors after initialization but rather can add actors anytime
+ * regardless of the object's state.
+ */
+function run_test()
+{
+ DebuggerServer.addActors("resource://test/pre_init_global_actors.js");
+ DebuggerServer.addActors("resource://test/pre_init_tab_actors.js");
+
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ DebuggerServer.addActors("resource://test/post_init_global_actors.js");
+ DebuggerServer.addActors("resource://test/post_init_tab_actors.js");
+
+ add_test(init);
+ add_test(test_pre_init_global_actor);
+ add_test(test_pre_init_tab_actor);
+ add_test(test_post_init_global_actor);
+ add_test(test_post_init_tab_actor);
+ add_test(test_stable_global_actor_instances);
+ add_test(close_client);
+ run_next_test();
+}
+
+function init()
+{
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect()
+ .then(() => gClient.listTabs())
+ .then(aResponse => {
+ gActors = aResponse;
+ run_next_test();
+ });
+}
+
+function test_pre_init_global_actor()
+{
+ gClient.request({ to: gActors.preInitGlobalActor, type: "ping" },
+ function onResponse(aResponse) {
+ do_check_eq(aResponse.message, "pong");
+ run_next_test();
+ }
+ );
+}
+
+function test_pre_init_tab_actor()
+{
+ gClient.request({ to: gActors.preInitTabActor, type: "ping" },
+ function onResponse(aResponse) {
+ do_check_eq(aResponse.message, "pong");
+ run_next_test();
+ }
+ );
+}
+
+function test_post_init_global_actor()
+{
+ gClient.request({ to: gActors.postInitGlobalActor, type: "ping" },
+ function onResponse(aResponse) {
+ do_check_eq(aResponse.message, "pong");
+ run_next_test();
+ }
+ );
+}
+
+function test_post_init_tab_actor()
+{
+ gClient.request({ to: gActors.postInitTabActor, type: "ping" },
+ function onResponse(aResponse) {
+ do_check_eq(aResponse.message, "pong");
+ run_next_test();
+ }
+ );
+}
+
+// Get the object object, from the server side, for a given actor ID
+function getActorInstance(connID, actorID) {
+ return DebuggerServer._connections[connID].getActor(actorID);
+}
+
+function test_stable_global_actor_instances()
+{
+ // Consider that there is only one connection,
+ // and the first one is ours
+ let connID = Object.keys(DebuggerServer._connections)[0];
+ let postInitGlobalActor = getActorInstance(connID, gActors.postInitGlobalActor);
+ let preInitGlobalActor = getActorInstance(connID, gActors.preInitGlobalActor);
+ gClient.listTabs(function onListTabs(aResponse) {
+ do_check_eq(postInitGlobalActor, getActorInstance(connID, aResponse.postInitGlobalActor));
+ do_check_eq(preInitGlobalActor, getActorInstance(connID, aResponse.preInitGlobalActor));
+ run_next_test();
+ });
+}
+
+function close_client() {
+ gClient.close().then(() => run_next_test());
+}
diff --git a/devtools/server/tests/unit/test_addon_reload.js b/devtools/server/tests/unit/test_addon_reload.js
new file mode 100644
index 000000000..0187fa3b3
--- /dev/null
+++ b/devtools/server/tests/unit/test_addon_reload.js
@@ -0,0 +1,98 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const protocol = require("devtools/shared/protocol");
+const {AddonManager} = require("resource://gre/modules/AddonManager.jsm");
+
+startupAddonsManager();
+
+function promiseAddonEvent(event) {
+ return new Promise(resolve => {
+ let listener = {
+ [event]: function (...args) {
+ AddonManager.removeAddonListener(listener);
+ resolve(args);
+ }
+ };
+
+ AddonManager.addAddonListener(listener);
+ });
+}
+
+function* findAddonInRootList(client, addonId) {
+ const result = yield client.listAddons();
+ const addonActor = result.addons.filter(addon => addon.id === addonId)[0];
+ ok(addonActor, `Found add-on actor for ${addonId}`);
+ return addonActor;
+}
+
+function* reloadAddon(client, addonActor) {
+ // The add-on will be re-installed after a successful reload.
+ const onInstalled = promiseAddonEvent("onInstalled");
+ yield client.request({to: addonActor.actor, type: "reload"});
+ yield onInstalled;
+}
+
+function getSupportFile(path) {
+ const allowMissing = false;
+ return do_get_file(path, allowMissing);
+}
+
+add_task(function* testReloadExitedAddon() {
+ const client = yield new Promise(resolve => {
+ get_chrome_actors(client => resolve(client));
+ });
+
+ // Install our main add-on to trigger reloads on.
+ const addonFile = getSupportFile("addons/web-extension");
+ const installedAddon = yield AddonManager.installTemporaryAddon(
+ addonFile);
+
+ // Install a decoy add-on.
+ const addonFile2 = getSupportFile("addons/web-extension2");
+ const installedAddon2 = yield AddonManager.installTemporaryAddon(
+ addonFile2);
+
+ let addonActor = yield findAddonInRootList(client, installedAddon.id);
+
+ yield reloadAddon(client, addonActor);
+
+ // Uninstall the decoy add-on, which should cause its actor to exit.
+ const onUninstalled = promiseAddonEvent("onUninstalled");
+ installedAddon2.uninstall();
+ const [uninstalledAddon] = yield onUninstalled;
+
+ // Try to re-list all add-ons after a reload.
+ // This was throwing an exception because of the exited actor.
+ const newAddonActor = yield findAddonInRootList(client, installedAddon.id);
+ equal(newAddonActor.id, addonActor.id);
+
+ // The actor id should be the same after the reload
+ equal(newAddonActor.actor, addonActor.actor);
+
+ const onAddonListChanged = new Promise((resolve) => {
+ client.addListener("addonListChanged", function listener() {
+ client.removeListener("addonListChanged", listener);
+ resolve();
+ });
+ });
+
+ // Install an upgrade version of the first add-on.
+ const addonUpgradeFile = getSupportFile("addons/web-extension-upgrade");
+ const upgradedAddon = yield AddonManager.installTemporaryAddon(
+ addonUpgradeFile);
+
+ // Waiting for addonListChanged unsolicited event
+ yield onAddonListChanged;
+
+ // re-list all add-ons after an upgrade.
+ const upgradedAddonActor = yield findAddonInRootList(client, upgradedAddon.id);
+ equal(upgradedAddonActor.id, addonActor.id);
+ // The actor id should be the same after the upgrade.
+ equal(upgradedAddonActor.actor, addonActor.actor);
+
+ // The addon metadata has been updated.
+ equal(upgradedAddonActor.name, "Test Addons Actor Upgrade");
+
+ yield close(client);
+});
diff --git a/devtools/server/tests/unit/test_addons_actor.js b/devtools/server/tests/unit/test_addons_actor.js
new file mode 100644
index 000000000..1815d43c6
--- /dev/null
+++ b/devtools/server/tests/unit/test_addons_actor.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const protocol = require("devtools/shared/protocol");
+const {AddonsActor} = require("devtools/server/actors/addons");
+const {AddonsFront} = require("devtools/shared/fronts/addons");
+
+startupAddonsManager();
+
+function* connect() {
+ const client = yield new Promise(resolve => {
+ get_chrome_actors(client => resolve(client));
+ });
+ const root = yield listTabs(client);
+ const addonsActor = root.addonsActor;
+ ok(addonsActor, "Got AddonsActor instance");
+
+ const addons = AddonsFront(client, {addonsActor});
+ return [client, addons];
+}
+
+add_task(function* testSuccessfulInstall() {
+ const [client, addons] = yield connect();
+
+ const allowMissing = false;
+ const usePlatformSeparator = true;
+ const addonPath = getFilePath("addons/web-extension",
+ allowMissing, usePlatformSeparator);
+ const installedAddon = yield addons.installTemporaryAddon(addonPath);
+ equal(installedAddon.id, "test-addons-actor@mozilla.org");
+ // The returned object is currently not a proper actor.
+ equal(installedAddon.actor, false);
+
+ const addonList = yield client.listAddons();
+ ok(addonList && addonList.addons && addonList.addons.map(a => a.name),
+ "Received list of add-ons");
+ const addon = addonList.addons.filter(a => a.id === installedAddon.id)[0];
+ ok(addon, "Test add-on appeared in root install list");
+
+ yield close(client);
+});
+
+add_task(function* testNonExistantPath() {
+ const [client, addons] = yield connect();
+
+ yield Assert.rejects(
+ addons.installTemporaryAddon("some-non-existant-path"),
+ /Could not install add-on.*Component returned failure/);
+
+ yield close(client);
+});
diff --git a/devtools/server/tests/unit/test_animation_name.js b/devtools/server/tests/unit/test_animation_name.js
new file mode 100644
index 000000000..4cd708fc4
--- /dev/null
+++ b/devtools/server/tests/unit/test_animation_name.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that AnimationPlayerActor.getName returns the right name depending on
+// the type of an animation and the various properties available on it.
+
+const { AnimationPlayerActor } = require("devtools/server/actors/animation");
+
+function run_test() {
+ // Mock a window with just the properties the AnimationPlayerActor uses.
+ let window = {
+ MutationObserver: function () {
+ this.observe = () => {};
+ },
+ Animation: function () {
+ this.effect = {target: getMockNode()};
+ },
+ CSSAnimation: function () {
+ this.effect = {target: getMockNode()};
+ },
+ CSSTransition: function () {
+ this.effect = {target: getMockNode()};
+ }
+ };
+
+ window.CSSAnimation.prototype = Object.create(window.Animation.prototype);
+ window.CSSTransition.prototype = Object.create(window.Animation.prototype);
+
+ // Helper to get a mock DOM node.
+ function getMockNode() {
+ return {
+ ownerDocument: {
+ defaultView: window
+ }
+ };
+ }
+
+ // Objects in this array should contain the following properties:
+ // - desc {String} For logging
+ // - animation {Object} An animation object instantiated from one of the mock
+ // window animation constructors.
+ // - props {Objet} Properties of this object will be added to the animation
+ // object.
+ // - expectedName {String} The expected name returned by
+ // AnimationPlayerActor.getName.
+ const TEST_DATA = [{
+ desc: "Animation with an id",
+ animation: new window.Animation(),
+ props: { id: "animation-id" },
+ expectedName: "animation-id"
+ }, {
+ desc: "Animation without an id",
+ animation: new window.Animation(),
+ props: {},
+ expectedName: ""
+ }, {
+ desc: "CSSTransition with an id",
+ animation: new window.CSSTransition(),
+ props: { id: "transition-with-id", transitionProperty: "width" },
+ expectedName: "transition-with-id"
+ }, {
+ desc: "CSSAnimation with an id",
+ animation: new window.CSSAnimation(),
+ props: { id: "animation-with-id", animationName: "move" },
+ expectedName: "animation-with-id"
+ }, {
+ desc: "CSSTransition without an id",
+ animation: new window.CSSTransition(),
+ props: { transitionProperty: "width" },
+ expectedName: "width"
+ }, {
+ desc: "CSSAnimation without an id",
+ animation: new window.CSSAnimation(),
+ props: { animationName: "move" },
+ expectedName: "move"
+ }];
+
+ for (let { desc, animation, props, expectedName } of TEST_DATA) {
+ do_print(desc);
+ for (let key in props) {
+ animation[key] = props[key];
+ }
+ let actor = AnimationPlayerActor({}, animation);
+ do_check_eq(actor.getName(), expectedName);
+ }
+}
diff --git a/devtools/server/tests/unit/test_animation_type.js b/devtools/server/tests/unit/test_animation_type.js
new file mode 100644
index 000000000..0f37755a4
--- /dev/null
+++ b/devtools/server/tests/unit/test_animation_type.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test the output of AnimationPlayerActor.getType().
+
+const { ANIMATION_TYPES, AnimationPlayerActor } =
+ require("devtools/server/actors/animation");
+
+function run_test() {
+ // Mock a window with just the properties the AnimationPlayerActor uses.
+ let window = {
+ MutationObserver: function () {
+ this.observe = () => {};
+ },
+ Animation: function () {
+ this.effect = {target: getMockNode()};
+ },
+ CSSAnimation: function () {
+ this.effect = {target: getMockNode()};
+ },
+ CSSTransition: function () {
+ this.effect = {target: getMockNode()};
+ }
+ };
+
+ window.CSSAnimation.prototype = Object.create(window.Animation.prototype);
+ window.CSSTransition.prototype = Object.create(window.Animation.prototype);
+
+ // Helper to get a mock DOM node.
+ function getMockNode() {
+ return {
+ ownerDocument: {
+ defaultView: window
+ }
+ };
+ }
+
+ // Objects in this array should contain the following properties:
+ // - desc {String} For logging
+ // - animation {Object} An animation object instantiated from one of the mock
+ // window animation constructors.
+ // - expectedType {String} The expected type returned by
+ // AnimationPlayerActor.getType.
+ const TEST_DATA = [{
+ desc: "Test CSSAnimation type",
+ animation: new window.CSSAnimation(),
+ expectedType: ANIMATION_TYPES.CSS_ANIMATION
+ }, {
+ desc: "Test CSSTransition type",
+ animation: new window.CSSTransition(),
+ expectedType: ANIMATION_TYPES.CSS_TRANSITION
+ }, {
+ desc: "Test ScriptAnimation type",
+ animation: new window.Animation(),
+ expectedType: ANIMATION_TYPES.SCRIPT_ANIMATION
+ }, {
+ desc: "Test unknown type",
+ animation: {effect: {target: getMockNode()}},
+ expectedType: ANIMATION_TYPES.UNKNOWN
+ }];
+
+ for (let { desc, animation, expectedType } of TEST_DATA) {
+ do_print(desc);
+ let actor = AnimationPlayerActor({}, animation);
+ do_check_eq(actor.getType(), expectedType);
+ }
+}
diff --git a/devtools/server/tests/unit/test_attach.js b/devtools/server/tests/unit/test_attach.js
new file mode 100644
index 000000000..a69db2c2b
--- /dev/null
+++ b/devtools/server/tests/unit/test_attach.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gClient;
+var gDebuggee;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = testGlobal("test-1");
+ DebuggerServer.addTestGlobal(gDebuggee);
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(function ([aType, aTraits]) {
+ attachTestTab(gClient, "test-1", function (aReply, aTabClient) {
+ test_attach(aTabClient);
+ });
+ });
+ do_test_pending();
+}
+
+function test_attach(aTabClient)
+{
+ aTabClient.attachThread({}, function (aResponse, aThreadClient) {
+ do_check_eq(aThreadClient.state, "paused");
+ aThreadClient.resume(cleanup);
+ });
+}
+
+function cleanup()
+{
+ gClient.addListener("closed", function (aEvent) {
+ do_test_finished();
+ });
+ gClient.close();
+}
diff --git a/devtools/server/tests/unit/test_blackboxing-01.js b/devtools/server/tests/unit/test_blackboxing-01.js
new file mode 100644
index 000000000..d5356c390
--- /dev/null
+++ b/devtools/server/tests/unit/test_blackboxing-01.js
@@ -0,0 +1,145 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test basic black boxing.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-black-box");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ testBlackBox();
+ });
+ });
+ do_test_pending();
+}
+
+const BLACK_BOXED_URL = "http://example.com/blackboxme.js";
+const SOURCE_URL = "http://example.com/source.js";
+
+const testBlackBox = Task.async(function* () {
+ let packet = yield executeOnNextTickAndWaitForPause(evalCode, gClient);
+ let source = gThreadClient.source(packet.frame.where.source);
+
+ yield setBreakpoint(source, {
+ line: 2
+ });
+ yield resume(gThreadClient);
+
+ const { sources } = yield getSources(gThreadClient);
+ let sourceClient = gThreadClient.source(
+ sources.filter(s => s.url == BLACK_BOXED_URL)[0]);
+ do_check_true(!sourceClient.isBlackBoxed,
+ "By default the source is not black boxed.");
+
+ // Test that we can step into `doStuff` when we are not black boxed.
+ yield runTest(
+ function onSteppedLocation(aLocation) {
+ do_check_eq(aLocation.source.url, BLACK_BOXED_URL);
+ do_check_eq(aLocation.line, 2);
+ },
+ function onDebuggerStatementFrames(aFrames) {
+ do_check_true(!aFrames.some(f => f.where.source.isBlackBoxed));
+ }
+ );
+
+ let blackBoxResponse = yield blackBox(sourceClient);
+ do_check_true(sourceClient.isBlackBoxed);
+
+ // Test that we step through `doStuff` when we are black boxed and its frame
+ // doesn't show up.
+ yield runTest(
+ function onSteppedLocation(aLocation) {
+ do_check_eq(aLocation.source.url, SOURCE_URL);
+ do_check_eq(aLocation.line, 4);
+ },
+ function onDebuggerStatementFrames(aFrames) {
+ for (let f of aFrames) {
+ if (f.where.source.url == BLACK_BOXED_URL) {
+ do_check_true(f.where.source.isBlackBoxed);
+ } else {
+ do_check_true(!f.where.source.isBlackBoxed);
+ }
+ }
+ }
+ );
+
+ let unBlackBoxResponse = yield unBlackBox(sourceClient);
+ do_check_true(!sourceClient.isBlackBoxed);
+
+ // Test that we can step into `doStuff` again.
+ yield runTest(
+ function onSteppedLocation(aLocation) {
+ do_check_eq(aLocation.source.url, BLACK_BOXED_URL);
+ do_check_eq(aLocation.line, 2);
+ },
+ function onDebuggerStatementFrames(aFrames) {
+ do_check_true(!aFrames.some(f => f.where.source.isBlackBoxed));
+ }
+ );
+
+ finishClient(gClient);
+});
+
+function evalCode() {
+ Components.utils.evalInSandbox(
+ "" + function doStuff(k) { // line 1
+ let arg = 15; // line 2 - Step in here
+ k(arg); // line 3
+ }, // line 4
+ gDebuggee,
+ "1.8",
+ BLACK_BOXED_URL,
+ 1
+ );
+
+ Components.utils.evalInSandbox(
+ "" + function runTest() { // line 1
+ doStuff( // line 2 - Break here
+ function (n) { // line 3 - Step through `doStuff` to here
+ debugger; // line 4
+ } // line 5
+ ); // line 6
+ } + "\n" // line 7
+ + "debugger;", // line 8
+ gDebuggee,
+ "1.8",
+ SOURCE_URL,
+ 1
+ );
+}
+
+const runTest = Task.async(function* (onSteppedLocation, onDebuggerStatementFrames) {
+ let packet = yield executeOnNextTickAndWaitForPause(gDebuggee.runTest,
+ gClient);
+ do_check_eq(packet.why.type, "breakpoint");
+
+ yield stepIn(gClient, gThreadClient);
+ yield stepIn(gClient, gThreadClient);
+ yield stepIn(gClient, gThreadClient);
+
+ const location = yield getCurrentLocation();
+ onSteppedLocation(location);
+
+ packet = yield resumeAndWaitForPause(gClient, gThreadClient);
+ do_check_eq(packet.why.type, "debuggerStatement");
+
+ let { frames } = yield getFrames(gThreadClient, 0, 100);
+ onDebuggerStatementFrames(frames);
+
+ return resume(gThreadClient);
+});
+
+const getCurrentLocation = Task.async(function* () {
+ const response = yield getFrames(gThreadClient, 0, 1);
+ return response.frames[0].where;
+});
diff --git a/devtools/server/tests/unit/test_blackboxing-02.js b/devtools/server/tests/unit/test_blackboxing-02.js
new file mode 100644
index 000000000..5bfb3641a
--- /dev/null
+++ b/devtools/server/tests/unit/test_blackboxing-02.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we don't hit breakpoints in black boxed sources, and that when we
+ * unblack box the source again, the breakpoint hasn't disappeared and we will
+ * hit it again.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-black-box");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_black_box();
+ });
+ });
+ do_test_pending();
+}
+
+const BLACK_BOXED_URL = "http://example.com/blackboxme.js";
+const SOURCE_URL = "http://example.com/source.js";
+
+function test_black_box()
+{
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.eval(aPacket.frame.actor, "doStuff", function (aResponse) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let obj = gThreadClient.pauseGrip(aPacket.why.frameFinished.return);
+ obj.getDefinitionSite(runWithSource);
+ });
+ });
+
+ function runWithSource(aPacket) {
+ let source = gThreadClient.source(aPacket.source);
+ source.setBreakpoint({
+ line: 2
+ }, function (aResponse) {
+ do_check_true(!aResponse.error, "Should be able to set breakpoint.");
+ gThreadClient.resume(test_black_box_breakpoint);
+ });
+ }
+ });
+
+ Components.utils.evalInSandbox(
+ "" + function doStuff(k) { // line 1
+ let arg = 15; // line 2 - Break here
+ k(arg); // line 3
+ }, // line 4
+ gDebuggee,
+ "1.8",
+ BLACK_BOXED_URL,
+ 1
+ );
+
+ Components.utils.evalInSandbox(
+ "" + function runTest() { // line 1
+ doStuff( // line 2
+ function (n) { // line 3
+ debugger; // line 5
+ } // line 6
+ ); // line 7
+ } // line 8
+ + "\n debugger;", // line 9
+ gDebuggee,
+ "1.8",
+ SOURCE_URL,
+ 1
+ );
+}
+
+function test_black_box_breakpoint() {
+ gThreadClient.getSources(function ({error, sources}) {
+ do_check_true(!error, "Should not get an error: " + error);
+ let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]);
+ sourceClient.blackBox(function ({error}) {
+ do_check_true(!error, "Should not get an error: " + error);
+
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aPacket.why.type, "debuggerStatement",
+ "We should pass over the breakpoint since the source is black boxed.");
+ gThreadClient.resume(test_unblack_box_breakpoint.bind(null, sourceClient));
+ });
+ gDebuggee.runTest();
+ });
+ });
+}
+
+function test_unblack_box_breakpoint(aSourceClient) {
+ aSourceClient.unblackBox(function ({error}) {
+ do_check_true(!error, "Should not get an error: " + error);
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aPacket.why.type, "breakpoint",
+ "We should hit the breakpoint again");
+
+ // We will hit the debugger statement on resume, so do this nastiness to skip over it.
+ gClient.addOneTimeListener(
+ "paused",
+ gThreadClient.resume.bind(
+ gThreadClient,
+ finishClient.bind(null, gClient)));
+ gThreadClient.resume();
+ });
+ gDebuggee.runTest();
+ });
+}
diff --git a/devtools/server/tests/unit/test_blackboxing-03.js b/devtools/server/tests/unit/test_blackboxing-03.js
new file mode 100644
index 000000000..48f178777
--- /dev/null
+++ b/devtools/server/tests/unit/test_blackboxing-03.js
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we don't stop at debugger statements inside black boxed sources.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gBpClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-black-box");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_black_box();
+ });
+ });
+ do_test_pending();
+}
+
+const BLACK_BOXED_URL = "http://example.com/blackboxme.js";
+const SOURCE_URL = "http://example.com/source.js";
+
+function test_black_box()
+{
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ source.setBreakpoint({
+ line: 4
+ }, function ({error}, bpClient) {
+ gBpClient = bpClient;
+ do_check_true(!error, "Should not get an error: " + error);
+ gThreadClient.resume(test_black_box_dbg_statement);
+ });
+ });
+
+ Components.utils.evalInSandbox(
+ "" + function doStuff(k) { // line 1
+ debugger; // line 2 - Break here
+ k(100); // line 3
+ }, // line 4
+ gDebuggee,
+ "1.8",
+ BLACK_BOXED_URL,
+ 1
+ );
+
+ Components.utils.evalInSandbox(
+ "" + function runTest() { // line 1
+ doStuff( // line 2
+ function (n) { // line 3
+ Math.abs(n); // line 4 - Break here
+ } // line 5
+ ); // line 6
+ } // line 7
+ + "\n debugger;", // line 8
+ gDebuggee,
+ "1.8",
+ SOURCE_URL,
+ 1
+ );
+}
+
+function test_black_box_dbg_statement() {
+ gThreadClient.getSources(function ({error, sources}) {
+ do_check_true(!error, "Should not get an error: " + error);
+ let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]);
+
+ sourceClient.blackBox(function ({error}) {
+ do_check_true(!error, "Should not get an error: " + error);
+
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aPacket.why.type, "breakpoint",
+ "We should pass over the debugger statement.");
+ gBpClient.remove(function ({error}) {
+ do_check_true(!error, "Should not get an error: " + error);
+ gThreadClient.resume(test_unblack_box_dbg_statement.bind(null, sourceClient));
+ });
+ });
+ gDebuggee.runTest();
+ });
+ });
+}
+
+function test_unblack_box_dbg_statement(aSourceClient) {
+ aSourceClient.unblackBox(function ({error}) {
+ do_check_true(!error, "Should not get an error: " + error);
+
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aPacket.why.type, "debuggerStatement",
+ "We should stop at the debugger statement again");
+ finishClient(gClient);
+ });
+ gDebuggee.runTest();
+ });
+}
diff --git a/devtools/server/tests/unit/test_blackboxing-04.js b/devtools/server/tests/unit/test_blackboxing-04.js
new file mode 100644
index 000000000..fbfaf2881
--- /dev/null
+++ b/devtools/server/tests/unit/test_blackboxing-04.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test behavior of blackboxing sources we are currently paused in.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-black-box");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_black_box();
+ });
+ });
+ do_test_pending();
+}
+
+const BLACK_BOXED_URL = "http://example.com/blackboxme.js";
+const SOURCE_URL = "http://example.com/source.js";
+
+function test_black_box()
+{
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.eval(aPacket.frame.actor, "doStuff", function (aResponse) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let obj = gThreadClient.pauseGrip(aPacket.why.frameFinished.return);
+ obj.getDefinitionSite(runWithSource);
+ });
+ });
+
+ function runWithSource(aPacket) {
+ let source = gThreadClient.source(aPacket.source);
+ source.setBreakpoint({
+ line: 2
+ }, function (aResponse) {
+ do_check_true(!aResponse.error, "Should be able to set breakpoint.");
+ test_black_box_paused();
+ });
+ }
+ });
+
+ Components.utils.evalInSandbox(
+ "" + function doStuff(k) { // line 1
+ debugger; // line 2
+ k(100); // line 3
+ }, // line 4
+ gDebuggee,
+ "1.8",
+ BLACK_BOXED_URL,
+ 1
+ );
+
+ Components.utils.evalInSandbox(
+ "" + function runTest() { // line 1
+ doStuff( // line 2
+ function (n) { // line 3
+ return n; // line 4
+ } // line 5
+ ); // line 6
+ } // line 7
+ + "\n runTest();", // line 8
+ gDebuggee,
+ "1.8",
+ SOURCE_URL,
+ 1
+ );
+}
+
+function test_black_box_paused() {
+ gThreadClient.getSources(function ({error, sources}) {
+ do_check_true(!error, "Should not get an error: " + error);
+ let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]);
+
+ sourceClient.blackBox(function ({error, pausedInSource}) {
+ do_check_true(!error, "Should not get an error: " + error);
+ do_check_true(pausedInSource, "We should be notified that we are currently paused in this source");
+ finishClient(gClient);
+ });
+ });
+}
diff --git a/devtools/server/tests/unit/test_blackboxing-05.js b/devtools/server/tests/unit/test_blackboxing-05.js
new file mode 100644
index 000000000..fa8142e87
--- /dev/null
+++ b/devtools/server/tests/unit/test_blackboxing-05.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test exceptions inside black boxed sources.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-black-box");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ // XXX: We have to do an executeSoon so that the error isn't caught and
+ // reported by DebuggerClient.requester (because we are using the local
+ // transport and share a stack) which causes the test to fail.
+ Services.tm.mainThread.dispatch({
+ run: test_black_box
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ });
+ });
+ do_test_pending();
+}
+
+const BLACK_BOXED_URL = "http://example.com/blackboxme.js";
+const SOURCE_URL = "http://example.com/source.js";
+
+function test_black_box()
+{
+ gClient.addOneTimeListener("paused", test_black_box_exception);
+
+ Components.utils.evalInSandbox(
+ "" + function doStuff(k) { // line 1
+ throw new Error("wu tang clan ain't nuthin' ta fuck wit"); // line 2
+ k(100); // line 3
+ }, // line 4
+ gDebuggee,
+ "1.8",
+ BLACK_BOXED_URL,
+ 1
+ );
+
+ Components.utils.evalInSandbox(
+ "" + function runTest() { // line 1
+ doStuff( // line 2
+ function (n) { // line 3
+ debugger; // line 4
+ } // line 5
+ ); // line 6
+ } // line 7
+ + "\ndebugger;\n" // line 8
+ + "try { runTest() } catch (ex) { }", // line 9
+ gDebuggee,
+ "1.8",
+ SOURCE_URL,
+ 1
+ );
+}
+
+function test_black_box_exception() {
+ gThreadClient.getSources(function ({error, sources}) {
+ do_check_true(!error, "Should not get an error: " + error);
+ let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]);
+
+ sourceClient.blackBox(function ({error}) {
+ do_check_true(!error, "Should not get an error: " + error);
+ gThreadClient.pauseOnExceptions(true);
+
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aPacket.frame.where.source.url, SOURCE_URL,
+ "We shouldn't pause while in the black boxed source.");
+ finishClient(gClient);
+ });
+
+ gThreadClient.resume();
+ });
+ });
+}
diff --git a/devtools/server/tests/unit/test_blackboxing-06.js b/devtools/server/tests/unit/test_blackboxing-06.js
new file mode 100644
index 000000000..9384f2cc2
--- /dev/null
+++ b/devtools/server/tests/unit/test_blackboxing-06.js
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can black box source mapped sources.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+const {SourceNode} = require("source-map");
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-black-box");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+
+ promise.resolve(setup_code())
+ .then(black_box_code)
+ .then(run_code)
+ .then(test_correct_location)
+ .then(null, function (error) {
+ do_check_true(false, "Should not get an error, got " + error);
+ })
+ .then(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+ do_test_pending();
+}
+
+function setup_code() {
+ let { code, map } = (new SourceNode(null, null, null, [
+ new SourceNode(1, 0, "a.js", "" + function a() {
+ return b();
+ }),
+ "\n",
+ new SourceNode(1, 0, "b.js", "" + function b() {
+ debugger; // Don't want to stop here.
+ return c();
+ }),
+ "\n",
+ new SourceNode(1, 0, "c.js", "" + function c() {
+ debugger; // Want to stop here.
+ }),
+ "\n"
+ ])).toStringWithSourceMap({
+ file: "abc.js",
+ sourceRoot: "http://example.com/"
+ });
+
+ code += "//# sourceMappingURL=data:text/json," + map.toString();
+
+ Components.utils.evalInSandbox(code,
+ gDebuggee,
+ "1.8",
+ "http://example.com/abc.js");
+}
+
+function black_box_code() {
+ const d = promise.defer();
+
+ gThreadClient.getSources(function ({ sources, error }) {
+ do_check_true(!error, "Shouldn't get an error getting sources");
+ const source = sources.filter((s) => {
+ return s.url.indexOf("b.js") !== -1;
+ })[0];
+ do_check_true(!!source, "We should have our source in the sources list");
+
+ gThreadClient.source(source).blackBox(function ({ error }) {
+ do_check_true(!error, "Should not get an error black boxing");
+ d.resolve(true);
+ });
+ });
+
+ return d.promise;
+}
+
+function run_code() {
+ const d = promise.defer();
+
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ d.resolve(aPacket);
+ gThreadClient.resume();
+ });
+ gDebuggee.a();
+
+ return d.promise;
+}
+
+function test_correct_location(aPacket) {
+ do_check_eq(aPacket.why.type, "debuggerStatement",
+ "Should hit a debugger statement.");
+ do_check_eq(aPacket.frame.where.source.url, "http://example.com/c.js",
+ "Should have skipped over the debugger statement in the black boxed source");
+}
diff --git a/devtools/server/tests/unit/test_blackboxing-07.js b/devtools/server/tests/unit/test_blackboxing-07.js
new file mode 100644
index 000000000..da3147021
--- /dev/null
+++ b/devtools/server/tests/unit/test_blackboxing-07.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that sources whose URL ends with ".min.js" automatically get black
+ * boxed.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-black-box");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-black-box", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ testBlackBox();
+ });
+ });
+ do_test_pending();
+}
+
+const BLACK_BOXED_URL = "http://example.com/black-boxed.min.js";
+const SOURCE_URL = "http://example.com/source.js";
+
+const testBlackBox = Task.async(function* () {
+ yield executeOnNextTickAndWaitForPause(evalCode, gClient);
+
+ const { sources } = yield getSources(gThreadClient);
+ equal(sources.length, 2);
+
+ const blackBoxedSource = sources.filter(s => s.url === BLACK_BOXED_URL)[0];
+ equal(blackBoxedSource.isBlackBoxed, true);
+
+ const regularSource = sources.filter(s => s.url === SOURCE_URL)[0];
+ equal(regularSource.isBlackBoxed, false);
+
+ finishClient(gClient);
+});
+
+function evalCode() {
+ Components.utils.evalInSandbox(
+ "" + function blackBoxed() {},
+ gDebuggee,
+ "1.8",
+ BLACK_BOXED_URL,
+ 1
+ );
+
+ Components.utils.evalInSandbox(
+ "" + function source() {}
+ + "\ndebugger;",
+ gDebuggee,
+ "1.8",
+ SOURCE_URL,
+ 1
+ );
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-01.js b/devtools/server/tests/unit/test_breakpoint-01.js
new file mode 100644
index 000000000..9a20257a3
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-01.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check basic breakpoint functionality.
+ */
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_breakpoint();
+ });
+ });
+}
+
+function test_simple_breakpoint()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ let location = {
+ line: gDebuggee.line0 + 3
+ };
+
+ source.setBreakpoint(location, function (aResponse, bpClient) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.source.actor, source.actor);
+ do_check_eq(aPacket.frame.where.line, location.line);
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.actors[0], bpClient.actor);
+ // Check that the breakpoint worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, undefined);
+
+ // Remove the breakpoint.
+ bpClient.remove(function (aResponse) {
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+
+ });
+
+ // Continue until the breakpoint is hit.
+ gThreadClient.resume();
+ });
+ });
+
+ Cu.evalInSandbox(
+ "var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "var a = 1;\n" + // line0 + 2
+ "var b = 2;\n", // line0 + 3
+ gDebuggee
+ );
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-02.js b/devtools/server/tests/unit/test_breakpoint-02.js
new file mode 100644
index 000000000..d2b220d83
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-02.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting breakpoints when the debuggee is running works.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_breakpoint_running();
+ });
+ });
+}
+
+function test_breakpoint_running()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let location = { line: gDebuggee.line0 + 3 };
+
+ gThreadClient.resume();
+
+ // Setting the breakpoint later should interrupt the debuggee.
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.why.type, "interrupted");
+ });
+
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ source.setBreakpoint(location, function (aResponse) {
+ // Eval scripts don't stick around long enough for the breakpoint to be set,
+ // so just make sure we got the expected response from the actor.
+ do_check_neq(aResponse.error, "noScript");
+
+ do_execute_soon(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ });
+
+ Cu.evalInSandbox(
+ "var line0 = Error().lineNumber;\n" +
+ "debugger;\n" +
+ "var a = 1;\n" + // line0 + 2
+ "var b = 2;\n", // line0 + 3
+ gDebuggee
+ );
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-03.js b/devtools/server/tests/unit/test_breakpoint-03.js
new file mode 100644
index 000000000..b1792866b
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-03.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting a breakpoint on a line without code will skip
+ * forward when we know the script isn't GCed (the debugger is connected,
+ * so it's kept alive).
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient,
+ "test-stack",
+ function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_skip_breakpoint();
+ });
+ });
+}
+
+var test_no_skip_breakpoint = Task.async(function*(source, location) {
+ let [aResponse, bpClient] = yield source.setBreakpoint(
+ Object.assign({}, location, { noSliding: true })
+ );
+
+ do_check_true(!aResponse.actualLocation);
+ do_check_eq(bpClient.location.line, gDebuggee.line0 + 3);
+ yield bpClient.remove();
+});
+
+var test_skip_breakpoint = function() {
+ gThreadClient.addOneTimeListener("paused", Task.async(function *(aEvent, aPacket) {
+ let location = { line: gDebuggee.line0 + 3 };
+ let source = gThreadClient.source(aPacket.frame.where.source);
+
+ // First, make sure that we can disable sliding with the
+ // `noSliding` option.
+ yield test_no_skip_breakpoint(source, location);
+
+ // Now make sure that the breakpoint properly slides forward one line.
+ const [aResponse, bpClient] = yield source.setBreakpoint(location);
+ do_check_true(!!aResponse.actualLocation);
+ do_check_eq(aResponse.actualLocation.source.actor, source.actor);
+ do_check_eq(aResponse.actualLocation.line, location.line + 1);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.source.actor, source.actor);
+ do_check_eq(aPacket.frame.where.line, location.line + 1);
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.actors[0], bpClient.actor);
+ // Check that the breakpoint worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, undefined);
+
+ // Remove the breakpoint.
+ bpClient.remove(function (aResponse) {
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ });
+
+ gThreadClient.resume();
+ }));
+
+ // Use `evalInSandbox` to make the debugger treat it as normal
+ // globally-scoped code, where breakpoint sliding rules apply.
+ Cu.evalInSandbox(
+ "var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "var a = 1;\n" + // line0 + 2
+ "// A comment.\n" + // line0 + 3
+ "var b = 2;", // line0 + 4
+ gDebuggee
+ );
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-04.js b/devtools/server/tests/unit/test_breakpoint-04.js
new file mode 100644
index 000000000..9004c092b
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-04.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting a breakpoint in a line in a child script works.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_child_breakpoint();
+ });
+ });
+}
+
+function test_child_breakpoint()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ let location = { line: gDebuggee.line0 + 3 };
+
+ source.setBreakpoint(location, function (aResponse, bpClient) {
+ // actualLocation is not returned when breakpoints don't skip forward.
+ do_check_eq(aResponse.actualLocation, undefined);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.source.actor, source.actor);
+ do_check_eq(aPacket.frame.where.line, location.line);
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.actors[0], bpClient.actor);
+ // Check that the breakpoint worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, undefined);
+
+ // Remove the breakpoint.
+ bpClient.remove(function (aResponse) {
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ });
+
+ // Continue until the breakpoint is hit.
+ gThreadClient.resume();
+ });
+
+ });
+
+ Cu.evalInSandbox(
+ "var line0 = Error().lineNumber;\n" +
+ "function foo() {\n" + // line0 + 1
+ " this.a = 1;\n" + // line0 + 2
+ " this.b = 2;\n" + // line0 + 3
+ "}\n" + // line0 + 4
+ "debugger;\n" + // line0 + 5
+ "foo();\n", // line0 + 6
+ gDebuggee
+ );
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-05.js b/devtools/server/tests/unit/test_breakpoint-05.js
new file mode 100644
index 000000000..9e04d0271
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-05.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting a breakpoint in a line without code in a child script
+ * will skip forward.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_child_skip_breakpoint();
+ });
+ });
+}
+
+function test_child_skip_breakpoint()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ let location = { line: gDebuggee.line0 + 3 };
+
+ source.setBreakpoint(location, function (aResponse, bpClient) {
+ // Check that the breakpoint has properly skipped forward one line.
+ do_check_eq(aResponse.actualLocation.source.actor, source.actor);
+ do_check_eq(aResponse.actualLocation.line, location.line + 1);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.source.actor, source.actor);
+ do_check_eq(aPacket.frame.where.line, location.line + 1);
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.actors[0], bpClient.actor);
+ // Check that the breakpoint worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, undefined);
+
+ // Remove the breakpoint.
+ bpClient.remove(function (aResponse) {
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ });
+
+ // Continue until the breakpoint is hit.
+ gThreadClient.resume();
+ });
+ });
+
+ Cu.evalInSandbox(
+ "var line0 = Error().lineNumber;\n" +
+ "function foo() {\n" + // line0 + 1
+ " this.a = 1;\n" + // line0 + 2
+ " // A comment.\n" + // line0 + 3
+ " this.b = 2;\n" + // line0 + 4
+ "}\n" + // line0 + 5
+ "debugger;\n" + // line0 + 6
+ "foo();\n", // line0 + 7
+ gDebuggee
+ );
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-06.js b/devtools/server/tests/unit/test_breakpoint-06.js
new file mode 100644
index 000000000..aa92b1a5f
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-06.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting a breakpoint in a line without code in a deeply-nested
+ * child script will skip forward.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_nested_breakpoint();
+ });
+ });
+}
+
+function test_nested_breakpoint()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ let location = { line: gDebuggee.line0 + 5 };
+
+ source.setBreakpoint(location, function (aResponse, bpClient) {
+ // Check that the breakpoint has properly skipped forward one line.
+ do_check_eq(aResponse.actualLocation.source.actor, source.actor);
+ do_check_eq(aResponse.actualLocation.line, location.line + 1);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.source.actor, source.actor);
+ do_check_eq(aPacket.frame.where.line, location.line + 1);
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.actors[0], bpClient.actor);
+ // Check that the breakpoint worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, undefined);
+
+ // Remove the breakpoint.
+ bpClient.remove(function (aResponse) {
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ });
+
+ // Continue until the breakpoint is hit.
+ gThreadClient.resume();
+ });
+
+ });
+
+ Cu.evalInSandbox(
+ "var line0 = Error().lineNumber;\n" +
+ "function foo() {\n" + // line0 + 1
+ " function bar() {\n" + // line0 + 2
+ " function baz() {\n" + // line0 + 3
+ " this.a = 1;\n" + // line0 + 4
+ " // A comment.\n" + // line0 + 5
+ " this.b = 2;\n" + // line0 + 6
+ " }\n" + // line0 + 7
+ " baz();\n" + // line0 + 8
+ " }\n" + // line0 + 9
+ " bar();\n" + // line0 + 10
+ "}\n" + // line0 + 11
+ "debugger;\n" + // line0 + 12
+ "foo();\n", // line0 + 13
+ gDebuggee
+ );
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-07.js b/devtools/server/tests/unit/test_breakpoint-07.js
new file mode 100644
index 000000000..008f1424d
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-07.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting a breakpoint in a line without code in the second child
+ * script will skip forward.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_second_child_skip_breakpoint();
+ });
+ });
+}
+
+function test_second_child_skip_breakpoint()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ let location = { line: gDebuggee.line0 + 6 };
+
+ source.setBreakpoint(location, function (aResponse, bpClient) {
+ // Check that the breakpoint has properly skipped forward one line.
+ do_check_eq(aResponse.actualLocation.source.actor, source.actor);
+ do_check_eq(aResponse.actualLocation.line, location.line + 1);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.source.actor, source.actor);
+ do_check_eq(aPacket.frame.where.line, location.line + 1);
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.actors[0], bpClient.actor);
+ // Check that the breakpoint worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, undefined);
+
+ // Remove the breakpoint.
+ bpClient.remove(function (aResponse) {
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ });
+
+ // Continue until the breakpoint is hit.
+ gThreadClient.resume();
+ });
+ });
+
+ Cu.evalInSandbox(
+ "var line0 = Error().lineNumber;\n" +
+ "function foo() {\n" + // line0 + 1
+ " bar();\n" + // line0 + 2
+ "}\n" + // line0 + 3
+ "function bar() {\n" + // line0 + 4
+ " this.a = 1;\n" + // line0 + 5
+ " // A comment.\n" + // line0 + 6
+ " this.b = 2;\n" + // line0 + 7
+ "}\n" + // line0 + 8
+ "debugger;\n" + // line0 + 9
+ "foo();\n", // line0 + 10
+ gDebuggee
+ );
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-08.js b/devtools/server/tests/unit/test_breakpoint-08.js
new file mode 100644
index 000000000..6215a61ac
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-08.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting a breakpoint in a line without code in a child script
+ * will skip forward, in a file with two scripts.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_child_skip_breakpoint();
+ });
+ });
+}
+
+function test_child_skip_breakpoint()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.eval(aPacket.frame.actor, "foo", function (aResponse) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let obj = gThreadClient.pauseGrip(aPacket.why.frameFinished.return);
+ obj.getDefinitionSite(runWithBreakpoint);
+ });
+ });
+
+ function runWithBreakpoint(aPacket) {
+ let source = gThreadClient.source(aPacket.source);
+ let location = { line: gDebuggee.line0 + 3 };
+
+ source.setBreakpoint(location, function (aResponse, bpClient) {
+ // Check that the breakpoint has properly skipped forward one line.
+ do_check_eq(aResponse.actualLocation.source.actor, source.actor);
+ do_check_eq(aResponse.actualLocation.line, location.line + 1);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.source.actor, source.actor);
+ do_check_eq(aPacket.frame.where.line, location.line + 1);
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.actors[0], bpClient.actor);
+ // Check that the breakpoint worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, undefined);
+
+ // Remove the breakpoint.
+ bpClient.remove(function (aResponse) {
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ });
+
+ // Continue until the breakpoint is hit.
+ gThreadClient.resume();
+ });
+ }
+ });
+
+ Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
+ "function foo() {\n" + // line0 + 1
+ " this.a = 1;\n" + // line0 + 2
+ " // A comment.\n" + // line0 + 3
+ " this.b = 2;\n" + // line0 + 4
+ "}\n", // line0 + 5
+ gDebuggee,
+ "1.7",
+ "script1.js");
+
+ Cu.evalInSandbox("var line1 = Error().lineNumber;\n" +
+ "debugger;\n" + // line1 + 1
+ "foo();\n", // line1 + 2
+ gDebuggee,
+ "1.7",
+ "script2.js");
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-09.js b/devtools/server/tests/unit/test_breakpoint-09.js
new file mode 100644
index 000000000..8bea375b9
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-09.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that removing a breakpoint works.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_remove_breakpoint();
+ });
+ });
+}
+
+function test_remove_breakpoint()
+{
+ let done = false;
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ let location = { line: gDebuggee.line0 + 2 };
+
+ source.setBreakpoint(location, function (aResponse, bpClient) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.source.actor, source.actor);
+ do_check_eq(aPacket.frame.where.line, location.line);
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.actors[0], bpClient.actor);
+ // Check that the breakpoint worked.
+ do_check_eq(gDebuggee.a, undefined);
+
+ // Remove the breakpoint.
+ bpClient.remove(function (aResponse) {
+ done = true;
+ gThreadClient.addOneTimeListener("paused",
+ function (aEvent, aPacket) {
+ // The breakpoint should not be hit again.
+ gThreadClient.resume(function () {
+ do_check_true(false);
+ });
+ });
+ gThreadClient.resume();
+ });
+
+ });
+ // Continue until the breakpoint is hit.
+ gThreadClient.resume();
+
+ });
+
+ });
+
+ Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
+ "function foo(stop) {\n" + // line0 + 1
+ " this.a = 1;\n" + // line0 + 2
+ " if (stop) return;\n" + // line0 + 3
+ " delete this.a;\n" + // line0 + 4
+ " foo(true);\n" + // line0 + 5
+ "}\n" + // line0 + 6
+ "debugger;\n" + // line1 + 7
+ "foo();\n", // line1 + 8
+ gDebuggee);
+ if (!done) {
+ do_check_true(false);
+ }
+ gClient.close().then(gCallback);
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-10.js b/devtools/server/tests/unit/test_breakpoint-10.js
new file mode 100644
index 000000000..c69576767
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-10.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that setting a breakpoint in a line with multiple entry points
+ * triggers no matter which entry point we reach.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_child_breakpoint();
+ });
+ });
+}
+
+function test_child_breakpoint()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ let location = { line: gDebuggee.line0 + 3 };
+
+ source.setBreakpoint(location, function (aResponse, bpClient) {
+ // actualLocation is not returned when breakpoints don't skip forward.
+ do_check_eq(aResponse.actualLocation, undefined);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.actors[0], bpClient.actor);
+ // Check that the breakpoint worked.
+ do_check_eq(gDebuggee.i, 0);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.actors[0], bpClient.actor);
+ // Check that the breakpoint worked.
+ do_check_eq(gDebuggee.i, 1);
+
+ // Remove the breakpoint.
+ bpClient.remove(function (aResponse) {
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ });
+
+ // Continue until the breakpoint is hit again.
+ gThreadClient.resume();
+
+ });
+ // Continue until the breakpoint is hit.
+ gThreadClient.resume();
+
+ });
+
+ });
+
+
+ Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "var a, i = 0;\n" + // line0 + 2
+ "for (i = 1; i <= 2; i++) {\n" + // line0 + 3
+ " a = i;\n" + // line0 + 4
+ "}\n", // line0 + 5
+ gDebuggee);
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-11.js b/devtools/server/tests/unit/test_breakpoint-11.js
new file mode 100644
index 000000000..480b95984
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-11.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that setting a breakpoint in a line with bytecodes in multiple
+ * scripts, sets the breakpoint in all of them (bug 793214).
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_child_breakpoint();
+ });
+ });
+}
+
+function test_child_breakpoint()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ let location = { line: gDebuggee.line0 + 2 };
+
+ source.setBreakpoint(location, function (aResponse, bpClient) {
+ // actualLocation is not returned when breakpoints don't skip forward.
+ do_check_eq(aResponse.actualLocation, undefined);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.actors[0], bpClient.actor);
+ // Check that the breakpoint worked.
+ do_check_eq(gDebuggee.a, undefined);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.actors[0], bpClient.actor);
+ // Check that the breakpoint worked.
+ do_check_eq(gDebuggee.a.b, 1);
+ do_check_eq(gDebuggee.res, undefined);
+
+ // Remove the breakpoint.
+ bpClient.remove(function (aResponse) {
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ });
+
+ // Continue until the breakpoint is hit again.
+ gThreadClient.resume();
+
+ });
+ // Continue until the breakpoint is hit.
+ gThreadClient.resume();
+
+ });
+
+ });
+
+
+ Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "var a = { b: 1, f: function() { return 2; } };\n" + // line0+2
+ "var res = a.f();\n", // line0 + 3
+ gDebuggee);
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-12.js b/devtools/server/tests/unit/test_breakpoint-12.js
new file mode 100644
index 000000000..fb147da9f
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-12.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that setting a breakpoint twice in a line without bytecodes works
+ * as expected.
+ */
+
+const NUM_BREAKPOINTS = 10;
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gBpActor;
+var gCount;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ gCount = 1;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_child_skip_breakpoint();
+ });
+ });
+}
+
+function test_child_skip_breakpoint()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ let location = { line: gDebuggee.line0 + 3};
+
+ source.setBreakpoint(location, function (aResponse, bpClient) {
+ // Check that the breakpoint has properly skipped forward one line.
+ do_check_eq(aResponse.actualLocation.source.actor, source.actor);
+ do_check_eq(aResponse.actualLocation.line, location.line + 1);
+ gBpActor = aResponse.actor;
+
+ // Set more breakpoints at the same location.
+ set_breakpoints(source, location);
+ });
+
+ });
+
+ Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
+ "function foo() {\n" + // line0 + 1
+ " this.a = 1;\n" + // line0 + 2
+ " // A comment.\n" + // line0 + 3
+ " this.b = 2;\n" + // line0 + 4
+ "}\n" + // line0 + 5
+ "debugger;\n" + // line0 + 6
+ "foo();\n", // line0 + 7
+ gDebuggee);
+}
+
+// Set many breakpoints at the same location.
+function set_breakpoints(source, location) {
+ do_check_neq(gCount, NUM_BREAKPOINTS);
+ source.setBreakpoint(location, function (aResponse, bpClient) {
+ // Check that the breakpoint has properly skipped forward one line.
+ do_check_eq(aResponse.actualLocation.source.actor, source.actor);
+ do_check_eq(aResponse.actualLocation.line, location.line + 1);
+ // Check that the same breakpoint actor was returned.
+ do_check_eq(aResponse.actor, gBpActor);
+
+ if (++gCount < NUM_BREAKPOINTS) {
+ set_breakpoints(source, location);
+ return;
+ }
+
+ // After setting all the breakpoints, check that only one has effectively
+ // remained.
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.source.actor, source.actor);
+ do_check_eq(aPacket.frame.where.line, location.line + 1);
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.actors[0], bpClient.actor);
+ // Check that the breakpoint worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, undefined);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // We don't expect any more pauses after the breakpoint was hit once.
+ do_check_true(false);
+ });
+ gThreadClient.resume(function () {
+ // Give any remaining breakpoints a chance to trigger.
+ do_timeout(1000, function () {
+ gClient.close().then(gCallback);
+ });
+ });
+
+ });
+ // Continue until the breakpoint is hit.
+ gThreadClient.resume();
+ });
+
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-13.js b/devtools/server/tests/unit/test_breakpoint-13.js
new file mode 100644
index 000000000..cdc4c9091
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-13.js
@@ -0,0 +1,115 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that execution doesn't pause twice while stepping, when encountering
+ * either a breakpoint or a debugger statement.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_breakpoint();
+ });
+ });
+}
+
+function test_simple_breakpoint()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ let location = { line: gDebuggee.line0 + 2 };
+
+ source.setBreakpoint(location, Task.async(function* (aResponse, bpClient) {
+ const testCallbacks = [
+ function (aPacket) {
+ // Check that the stepping worked.
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ },
+ function (aPacket) {
+ // Entered the foo function call frame.
+ do_check_eq(aPacket.frame.where.line, location.line);
+ do_check_neq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ },
+ function (aPacket) {
+ // At the end of the foo function call frame.
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
+ do_check_neq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ },
+ function (aPacket) {
+ // Check that the breakpoint wasn't the reason for this pause, but
+ // that the frame is about to be popped while stepping.
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
+ do_check_neq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ do_check_eq(aPacket.why.frameFinished.return.type, "undefined");
+ },
+ function (aPacket) {
+ // The foo function call frame was just popped from the stack.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, undefined);
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ do_check_eq(aPacket.poppedFrames.length, 1);
+ },
+ function (aPacket) {
+ // Check that the debugger statement wasn't the reason for this pause.
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 6);
+ do_check_neq(aPacket.why.type, "debuggerStatement");
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ },
+ function (aPacket) {
+ // Check that the debugger statement wasn't the reason for this pause.
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 7);
+ do_check_neq(aPacket.why.type, "debuggerStatement");
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ },
+ ];
+
+ for (let callback of testCallbacks) {
+ let waiter = waitForPause(gThreadClient);
+ gThreadClient.stepIn();
+ let packet = yield waiter;
+ callback(packet);
+ }
+
+ // Remove the breakpoint and finish.
+ let waiter = waitForPause(gThreadClient);
+ gThreadClient.stepIn();
+ yield waiter;
+ bpClient.remove(() => gThreadClient.resume(() => gClient.close().then(gCallback)));
+ }));
+ });
+
+ Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
+ "function foo() {\n" + // line0 + 1
+ " this.a = 1;\n" + // line0 + 2 <-- Breakpoint is set here.
+ "}\n" + // line0 + 3
+ "debugger;\n" + // line0 + 4
+ "foo();\n" + // line0 + 5
+ "debugger;\n" + // line0 + 6
+ "var b = 2;\n", // line0 + 7
+ gDebuggee);
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-14.js b/devtools/server/tests/unit/test_breakpoint-14.js
new file mode 100644
index 000000000..aa86975b6
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-14.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that a breakpoint or a debugger statement cause execution to pause even
+ * in a stepped-over function.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_breakpoint();
+ });
+ });
+}
+
+function test_simple_breakpoint()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ let location = { line: gDebuggee.line0 + 2 };
+
+ source.setBreakpoint(location, Task.async(function* (aResponse, bpClient) {
+ const testCallbacks = [
+ function (aPacket) {
+ // Check that the stepping worked.
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ },
+ function (aPacket) {
+ // Reached the breakpoint.
+ do_check_eq(aPacket.frame.where.line, location.line);
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_neq(aPacket.why.type, "resumeLimit");
+ },
+ function (aPacket) {
+ // Stepped to the closing brace of the function.
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ },
+ function (aPacket) {
+ // The frame is about to be popped while stepping.
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
+ do_check_neq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ do_check_eq(aPacket.why.frameFinished.return.type, "undefined");
+ },
+ function (aPacket) {
+ // The foo function call frame was just popped from the stack.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, undefined);
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ do_check_eq(aPacket.poppedFrames.length, 1);
+ },
+ function (aPacket) {
+ // Check that the debugger statement wasn't the reason for this pause.
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 6);
+ do_check_neq(aPacket.why.type, "debuggerStatement");
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ },
+ function (aPacket) {
+ // Check that the debugger statement wasn't the reason for this pause.
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 7);
+ do_check_neq(aPacket.why.type, "debuggerStatement");
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ },
+ ];
+
+ for (let callback of testCallbacks) {
+ let waiter = waitForPause(gThreadClient);
+ gThreadClient.stepOver();
+ let packet = yield waiter;
+ callback(packet);
+ }
+
+ // Remove the breakpoint and finish.
+ let waiter = waitForPause(gThreadClient);
+ gThreadClient.stepOver();
+ yield waiter;
+ bpClient.remove(() => gThreadClient.resume(() => gClient.close().then(gCallback)));
+ }));
+ });
+
+ Cu.evalInSandbox("var line0 = Error().lineNumber;\n" +
+ "function foo() {\n" + // line0 + 1
+ " this.a = 1;\n" + // line0 + 2 <-- Breakpoint is set here.
+ "}\n" + // line0 + 3
+ "debugger;\n" + // line0 + 4
+ "foo();\n" + // line0 + 5
+ "debugger;\n" + // line0 + 6
+ "var b = 2;\n", // line0 + 7
+ gDebuggee);
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-15.js b/devtools/server/tests/unit/test_breakpoint-15.js
new file mode 100644
index 000000000..6a3ab7c6f
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-15.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that adding a breakpoint in the same place returns the same actor.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ testSameBreakpoint();
+ });
+ });
+ do_test_pending();
+}
+
+const SOURCE_URL = "http://example.com/source.js";
+
+const testSameBreakpoint = Task.async(function* () {
+ let packet = yield executeOnNextTickAndWaitForPause(evalCode, gClient);
+ let source = gThreadClient.source(packet.frame.where.source);
+
+ // Whole line
+ let wholeLineLocation = {
+ line: 2
+ };
+
+ let [firstResponse, firstBpClient] = yield setBreakpoint(source, wholeLineLocation);
+ let [secondResponse, secondBpClient] = yield setBreakpoint(source, wholeLineLocation);
+
+ do_check_eq(firstBpClient.actor, secondBpClient.actor, "Should get the same actor w/ whole line breakpoints");
+
+ // Specific column
+
+ let columnLocation = {
+ line: 2,
+ column: 6
+ };
+
+ [firstResponse, firstBpClient] = yield setBreakpoint(source, columnLocation);
+ [secondResponse, secondBpClient] = yield setBreakpoint(source, columnLocation);
+
+ do_check_eq(secondBpClient.actor, secondBpClient.actor, "Should get the same actor column breakpoints");
+
+ finishClient(gClient);
+});
+
+function evalCode() {
+ Components.utils.evalInSandbox(
+ "" + function doStuff(k) { // line 1
+ let arg = 15; // line 2 - Step in here
+ k(arg); // line 3
+ } + "\n" // line 4
+ + "debugger;", // line 5
+ gDebuggee,
+ "1.8",
+ SOURCE_URL,
+ 1
+ );
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-16.js b/devtools/server/tests/unit/test_breakpoint-16.js
new file mode 100644
index 000000000..43a9086ec
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-16.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that we can set breakpoints in columns, not just lines.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-breakpoints", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient,
+ "test-breakpoints",
+ function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_column_breakpoint();
+ });
+ });
+}
+
+function test_column_breakpoint()
+{
+ // Debugger statement
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ let location = {
+ line: gDebuggee.line0 + 1,
+ column: 55
+ };
+ let timesBreakpointHit = 0;
+
+ source.setBreakpoint(location, function (aResponse, bpClient) {
+ gThreadClient.addListener("paused", function onPaused(aEvent, aPacket) {
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.why.actors[0], bpClient.actor);
+ do_check_eq(aPacket.frame.where.source.actor, source.actor);
+ do_check_eq(aPacket.frame.where.line, location.line);
+ do_check_eq(aPacket.frame.where.column, location.column);
+
+ do_check_eq(gDebuggee.acc, timesBreakpointHit);
+ do_check_eq(aPacket.frame.environment.bindings.variables.i.value,
+ timesBreakpointHit);
+
+ if (++timesBreakpointHit === 3) {
+ gThreadClient.removeListener("paused", onPaused);
+ bpClient.remove(function (aResponse) {
+ gThreadClient.resume(() => gClient.close().then(gCallback));
+ });
+ } else {
+ gThreadClient.resume();
+ }
+ });
+
+ // Continue until the breakpoint is hit.
+ gThreadClient.resume();
+ });
+
+ });
+
+
+ Components.utils.evalInSandbox(
+ "var line0 = Error().lineNumber;\n" +
+ "(function () { debugger; this.acc = 0; for (var i = 0; i < 3; i++) this.acc++; }());",
+ gDebuggee
+ );
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-17.js b/devtools/server/tests/unit/test_breakpoint-17.js
new file mode 100644
index 000000000..944627e61
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-17.js
@@ -0,0 +1,120 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that when we add 2 breakpoints to the same line at different columns and
+ * then remove one of them, we don't remove them both.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, do_test_finished);
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-breakpoints", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-breakpoints", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_breakpoints_columns();
+ });
+ });
+}
+
+const code =
+"(" + function (global) {
+ global.foo = function () {
+ Math.abs(-1); Math.log(0.5);
+ debugger;
+ };
+ debugger;
+} + "(this))";
+
+const firstLocation = {
+ line: 3,
+ column: 4
+};
+
+const secondLocation = {
+ line: 3,
+ column: 18
+};
+
+function test_breakpoints_columns() {
+ gClient.addOneTimeListener("paused", set_breakpoints);
+
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8", "http://example.com/", 1);
+}
+
+function set_breakpoints(aEvent, aPacket) {
+ let first, second;
+ let source = gThreadClient.source(aPacket.frame.where.source);
+
+ source.setBreakpoint(firstLocation, function ({ error, actualLocation },
+ aBreakpointClient) {
+ do_check_true(!error, "Should not get an error setting the breakpoint");
+ do_check_true(!actualLocation, "Should not get an actualLocation");
+ first = aBreakpointClient;
+
+ source.setBreakpoint(secondLocation, function ({ error, actualLocation },
+ aBreakpointClient) {
+ do_check_true(!error, "Should not get an error setting the breakpoint");
+ do_check_true(!actualLocation, "Should not get an actualLocation");
+ second = aBreakpointClient;
+
+ test_different_actors(first, second);
+ });
+ });
+}
+
+function test_different_actors(aFirst, aSecond) {
+ do_check_neq(aFirst.actor, aSecond.actor,
+ "Each breakpoint should have a different actor");
+ test_remove_one(aFirst, aSecond);
+}
+
+function test_remove_one(aFirst, aSecond) {
+ aFirst.remove(function ({error}) {
+ do_check_true(!error, "Should not get an error removing a breakpoint");
+
+ let hitSecond;
+ gClient.addListener("paused", function _onPaused(aEvent, {why, frame}) {
+ if (why.type == "breakpoint") {
+ hitSecond = true;
+ do_check_eq(why.actors.length, 1,
+ "Should only be paused because of one breakpoint actor");
+ do_check_eq(why.actors[0], aSecond.actor,
+ "Should be paused because of the correct breakpoint actor");
+ do_check_eq(frame.where.line, secondLocation.line,
+ "Should be at the right line");
+ do_check_eq(frame.where.column, secondLocation.column,
+ "Should be at the right column");
+ gThreadClient.resume();
+ return;
+ }
+
+ if (why.type == "debuggerStatement") {
+ gClient.removeListener("paused", _onPaused);
+ do_check_true(hitSecond,
+ "We should still hit `second`, but not `first`.");
+
+ gClient.close().then(gCallback);
+ return;
+ }
+
+ do_check_true(false, "Should never get here");
+ });
+
+ gThreadClient.resume(() => gDebuggee.foo());
+ });
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-18.js b/devtools/server/tests/unit/test_breakpoint-18.js
new file mode 100644
index 000000000..d153d3eff
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-18.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that we only break on offsets that are entry points for the line we are
+ * breaking on. Bug 907278.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-breakpoints", aServer);
+ gDebuggee.console = { log: x => void x };
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient,
+ "test-breakpoints",
+ function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ setUpCode();
+ });
+ });
+}
+
+function setUpCode() {
+ gClient.addOneTimeListener("paused", setBreakpoint);
+ Cu.evalInSandbox(
+ "debugger;\n" +
+ function test() {
+ console.log("foo bar");
+ debugger;
+ },
+ gDebuggee,
+ "1.8",
+ "http://example.com/",
+ 1
+ );
+}
+
+function setBreakpoint(aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ gClient.addOneTimeListener("resumed", runCode);
+
+ source.setBreakpoint({ line: 2 }, ({ error }) => {
+ do_check_true(!error);
+ gThreadClient.resume();
+ });
+}
+
+function runCode() {
+ gClient.addOneTimeListener("paused", testBPHit);
+ gDebuggee.test();
+}
+
+function testBPHit(event, { why }) {
+ do_check_eq(why.type, "breakpoint");
+ gClient.addOneTimeListener("paused", testDbgStatement);
+ gThreadClient.resume();
+}
+
+function testDbgStatement(event, { why }) {
+ // Should continue to the debugger statement.
+ do_check_eq(why.type, "debuggerStatement");
+ // Not break on another offset from the same line (that isn't an entry point
+ // to the line)
+ do_check_neq(why.type, "breakpoint");
+ gClient.close().then(gCallback);
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-19.js b/devtools/server/tests/unit/test_breakpoint-19.js
new file mode 100644
index 000000000..da04a5268
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-19.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that setting a breakpoint in a not-yet-existing script doesn't throw
+ * an error (see bug 897567). Also make sure that this breakpoint works.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-breakpoints", aServer);
+ gDebuggee.console = { log: x => void x };
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient,
+ "test-breakpoints",
+ function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ testBreakpoint();
+ });
+ });
+}
+
+const URL = "test.js";
+
+function setUpCode() {
+ Cu.evalInSandbox(
+ "" + function test() { // 1
+ var a = 1; // 2
+ debugger; // 3
+ } + // 4
+ "\ndebugger;", // 5
+ gDebuggee,
+ "1.8",
+ URL
+ );
+}
+
+const testBreakpoint = Task.async(function* () {
+ let source = yield getSource(gThreadClient, URL);
+ let [response, bpClient] = yield setBreakpoint(source, {line: 2});
+ ok(!response.error);
+
+ let actor = response.actor;
+ ok(actor);
+
+ yield executeOnNextTickAndWaitForPause(setUpCode, gClient);
+ yield resume(gThreadClient);
+
+ let packet = yield executeOnNextTickAndWaitForPause(gDebuggee.test, gClient);
+ equal(packet.why.type, "breakpoint");
+ notEqual(packet.why.actors.indexOf(actor), -1);
+
+ finishClient(gClient);
+});
diff --git a/devtools/server/tests/unit/test_breakpoint-20.js b/devtools/server/tests/unit/test_breakpoint-20.js
new file mode 100644
index 000000000..b70282dae
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-20.js
@@ -0,0 +1,108 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Verify that when two of the "same" source are loaded concurrently (like e10s
+ * frame scripts), breakpoints get hit in scripts defined by all sources.
+ */
+
+var gDebuggee;
+var gClient;
+var gTraceClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-breakpoints");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestThread(gClient, "test-breakpoints", testBreakpoint);
+ });
+ do_test_pending();
+}
+
+const testBreakpoint = Task.async(function* (threadResponse, tabClient, threadClient, tabResponse) {
+ evalSetupCode();
+
+ // Load the test source once.
+
+ evalTestCode();
+ equal(gDebuggee.functions.length, 1,
+ "The test code should have added a function.");
+
+ // Set a breakpoint in the test source.
+
+ const source = yield getSource(threadClient, "test.js");
+ const [response, bpClient] = yield setBreakpoint(source, {
+ line: 3
+ });
+ ok(!response.error, "Shouldn't get an error setting the BP.");
+ ok(!response.actualLocation,
+ "Shouldn't get an actualLocation, the location we provided was good.");
+ const bpActor = response.actor;
+
+ yield resume(threadClient);
+
+ // Load the test source again.
+
+ evalTestCode();
+ equal(gDebuggee.functions.length, 2,
+ "The test code should have added another function.");
+
+ // Should hit our breakpoint in a script defined by the first instance of the
+ // test source.
+
+ const bpPause1 = yield executeOnNextTickAndWaitForPause(gDebuggee.functions[0],
+ gClient);
+ equal(bpPause1.why.type, "breakpoint",
+ "Should pause because of hitting our breakpoint (not debugger statement).");
+ equal(bpPause1.why.actors[0], bpActor,
+ "And the breakpoint actor should be correct.");
+ const dbgStmtPause1 = yield executeOnNextTickAndWaitForPause(() => resume(threadClient),
+ gClient);
+ equal(dbgStmtPause1.why.type, "debuggerStatement",
+ "And we should hit the debugger statement after the pause.");
+ yield resume(threadClient);
+
+ // Should also hit our breakpoint in a script defined by the second instance
+ // of the test source.
+
+ const bpPause2 = yield executeOnNextTickAndWaitForPause(gDebuggee.functions[1],
+ gClient);
+ equal(bpPause2.why.type, "breakpoint",
+ "Should pause because of hitting our breakpoint (not debugger statement).");
+ equal(bpPause2.why.actors[0], bpActor,
+ "And the breakpoint actor should be correct.");
+ const dbgStmtPause2 = yield executeOnNextTickAndWaitForPause(() => resume(threadClient),
+ gClient);
+ equal(dbgStmtPause2.why.type, "debuggerStatement",
+ "And we should hit the debugger statement after the pause.");
+
+ finishClient(gClient);
+});
+
+function evalSetupCode() {
+ Cu.evalInSandbox(
+ "this.functions = [];",
+ gDebuggee,
+ "1.8",
+ "setup.js",
+ 1
+ );
+}
+
+function evalTestCode() {
+ Cu.evalInSandbox(
+ ` // 1
+ this.functions.push(function () { // 2
+ var setBreakpointHere = 1; // 3
+ debugger; // 4
+ }); // 5
+ `,
+ gDebuggee,
+ "1.8",
+ "test.js",
+ 1
+ );
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-21.js b/devtools/server/tests/unit/test_breakpoint-21.js
new file mode 100644
index 000000000..e5f2e9e4a
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-21.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 1122064 - make sure that scripts introduced via onNewScripts
+ * properly populate the `ScriptStore` with all there nested child
+ * scripts, so you can set breakpoints on deeply nested scripts
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-breakpoints", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient,
+ "test-breakpoints",
+ function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test();
+ });
+ });
+}
+
+const test = Task.async(function* () {
+ // Populate the `ScriptStore` so that we only test that the script
+ // is added through `onNewScript`
+ yield getSources(gThreadClient);
+
+ let packet = yield executeOnNextTickAndWaitForPause(evalCode, gClient);
+ let source = gThreadClient.source(packet.frame.where.source);
+ let location = {
+ line: gDebuggee.line0 + 8
+ };
+
+ let [res, bpClient] = yield setBreakpoint(source, location);
+ ok(!res.error);
+
+ yield resume(gThreadClient);
+ packet = yield waitForPause(gClient);
+ do_check_eq(packet.type, "paused");
+ do_check_eq(packet.why.type, "breakpoint");
+ do_check_eq(packet.why.actors[0], bpClient.actor);
+ do_check_eq(packet.frame.where.source.actor, source.actor);
+ do_check_eq(packet.frame.where.line, location.line);
+
+ yield resume(gThreadClient);
+ finishClient(gClient);
+});
+
+function evalCode() {
+ // Start a new script
+ Components.utils.evalInSandbox(
+ "var line0 = Error().lineNumber;\n(" + function () {
+ debugger;
+ var a = (function () {
+ return (function () {
+ return (function () {
+ return (function () {
+ return (function () {
+ var x = 10; // This line gets a breakpoint
+ return 1;
+ })();
+ })();
+ })();
+ })();
+ })();
+ } + ")()",
+ gDebuggee
+ );
+}
diff --git a/devtools/server/tests/unit/test_breakpoint-actor-map.js b/devtools/server/tests/unit/test_breakpoint-actor-map.js
new file mode 100644
index 000000000..d1d149648
--- /dev/null
+++ b/devtools/server/tests/unit/test_breakpoint-actor-map.js
@@ -0,0 +1,180 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the functionality of the BreakpointActorMap object.
+
+const { BreakpointActorMap } = require("devtools/server/actors/script");
+
+function run_test() {
+ test_get_actor();
+ test_set_actor();
+ test_delete_actor();
+ test_find_actors();
+ test_duplicate_actors();
+}
+
+function test_get_actor() {
+ let bpStore = new BreakpointActorMap();
+ let location = {
+ originalSourceActor: { actor: "actor1" },
+ originalLine: 3
+ };
+ let columnLocation = {
+ originalSourceActor: { actor: "actor2" },
+ originalLine: 5,
+ originalColumn: 15
+ };
+
+ // Shouldn't have breakpoint
+ do_check_eq(null, bpStore.getActor(location),
+ "Breakpoint not added and shouldn't exist.");
+
+ bpStore.setActor(location, {});
+ do_check_true(!!bpStore.getActor(location),
+ "Breakpoint added but not found in Breakpoint Store.");
+
+ bpStore.deleteActor(location);
+ do_check_eq(null, bpStore.getActor(location),
+ "Breakpoint removed but still exists.");
+
+ // Same checks for breakpoint with a column
+ do_check_eq(null, bpStore.getActor(columnLocation),
+ "Breakpoint with column not added and shouldn't exist.");
+
+ bpStore.setActor(columnLocation, {});
+ do_check_true(!!bpStore.getActor(columnLocation),
+ "Breakpoint with column added but not found in Breakpoint Store.");
+
+ bpStore.deleteActor(columnLocation);
+ do_check_eq(null, bpStore.getActor(columnLocation),
+ "Breakpoint with column removed but still exists in Breakpoint Store.");
+}
+
+function test_set_actor() {
+ // Breakpoint with column
+ let bpStore = new BreakpointActorMap();
+ let location = {
+ originalSourceActor: { actor: "actor1" },
+ originalLine: 10,
+ originalColumn: 9
+ };
+ bpStore.setActor(location, {});
+ do_check_true(!!bpStore.getActor(location),
+ "We should have the column breakpoint we just added");
+
+ // Breakpoint without column (whole line breakpoint)
+ location = {
+ originalSourceActor: { actor: "actor2" },
+ originalLine: 103
+ };
+ bpStore.setActor(location, {});
+ do_check_true(!!bpStore.getActor(location),
+ "We should have the whole line breakpoint we just added");
+}
+
+function test_delete_actor() {
+ // Breakpoint with column
+ let bpStore = new BreakpointActorMap();
+ let location = {
+ originalSourceActor: { actor: "actor1" },
+ originalLine: 10,
+ originalColumn: 9
+ };
+ bpStore.setActor(location, {});
+ bpStore.deleteActor(location);
+ do_check_eq(bpStore.getActor(location), null,
+ "We should not have the column breakpoint anymore");
+
+ // Breakpoint without column (whole line breakpoint)
+ location = {
+ originalSourceActor: { actor: "actor2" },
+ originalLine: 103
+ };
+ bpStore.setActor(location, {});
+ bpStore.deleteActor(location);
+ do_check_eq(bpStore.getActor(location), null,
+ "We should not have the whole line breakpoint anymore");
+}
+
+function test_find_actors() {
+ let bps = [
+ { originalSourceActor: { actor: "actor1" }, originalLine: 10 },
+ { originalSourceActor: { actor: "actor1" }, originalLine: 10, originalColumn: 3 },
+ { originalSourceActor: { actor: "actor1" }, originalLine: 10, originalColumn: 10 },
+ { originalSourceActor: { actor: "actor1" }, originalLine: 23, originalColumn: 89 },
+ { originalSourceActor: { actor: "actor2" }, originalLine: 10, originalColumn: 1 },
+ { originalSourceActor: { actor: "actor2" }, originalLine: 20, originalColumn: 5 },
+ { originalSourceActor: { actor: "actor2" }, originalLine: 30, originalColumn: 34 },
+ { originalSourceActor: { actor: "actor2" }, originalLine: 40, originalColumn: 56 }
+ ];
+
+ let bpStore = new BreakpointActorMap();
+
+ for (let bp of bps) {
+ bpStore.setActor(bp, bp);
+ }
+
+ // All breakpoints
+
+ let bpSet = new Set(bps);
+ for (let bp of bpStore.findActors()) {
+ bpSet.delete(bp);
+ }
+ do_check_eq(bpSet.size, 0,
+ "Should be able to iterate over all breakpoints");
+
+ // Breakpoints by URL
+
+ bpSet = new Set(bps.filter(bp => { return bp.originalSourceActor.actorID === "actor1"; }));
+ for (let bp of bpStore.findActors({ originalSourceActor: { actorID: "actor1" } })) {
+ bpSet.delete(bp);
+ }
+ do_check_eq(bpSet.size, 0,
+ "Should be able to filter the iteration by url");
+
+ // Breakpoints by URL and line
+
+ bpSet = new Set(bps.filter(bp => { return bp.originalSourceActor.actorID === "actor1" && bp.originalLine === 10; }));
+ let first = true;
+ for (let bp of bpStore.findActors({ originalSourceActor: { actorID: "actor1" }, originalLine: 10 })) {
+ if (first) {
+ do_check_eq(bp.originalColumn, undefined,
+ "Should always get the whole line breakpoint first");
+ first = false;
+ } else {
+ do_check_neq(bp.originalColumn, undefined,
+ "Should not get the whole line breakpoint any time other than first.");
+ }
+ bpSet.delete(bp);
+ }
+ do_check_eq(bpSet.size, 0,
+ "Should be able to filter the iteration by url and line");
+}
+
+function test_duplicate_actors() {
+ let bpStore = new BreakpointActorMap();
+
+ // Breakpoint with column
+ let location = {
+ originalSourceActor: { actorID: "foo-actor" },
+ originalLine: 10,
+ originalColumn: 9
+ };
+ bpStore.setActor(location, {});
+ bpStore.setActor(location, {});
+ do_check_eq(bpStore.size, 1, "We should have only 1 column breakpoint");
+ bpStore.deleteActor(location);
+
+ // Breakpoint without column (whole line breakpoint)
+ location = {
+ originalSourceActor: { actorID: "foo-actor" },
+ originalLine: 15
+ };
+ bpStore.setActor(location, {});
+ bpStore.setActor(location, {});
+ do_check_eq(bpStore.size, 1, "We should have only 1 whole line breakpoint");
+ bpStore.deleteActor(location);
+}
diff --git a/devtools/server/tests/unit/test_client_close.js b/devtools/server/tests/unit/test_client_close.js
new file mode 100644
index 000000000..84747e85b
--- /dev/null
+++ b/devtools/server/tests/unit/test_client_close.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gClient;
+var gDebuggee;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = testGlobal("test-1");
+ DebuggerServer.addTestGlobal(gDebuggee);
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(function (aType, aTraits) {
+ attachTestTab(gClient, "test-1", function (aReply, aTabClient) {
+ test_close(transport);
+ });
+ });
+ do_test_pending();
+}
+
+function test_close(aTransport)
+{
+ // Check that, if we fake a transport shutdown
+ // (like if a device is unplugged)
+ // the client is automatically closed,
+ // and we can still call client.close.
+ let onClosed = function () {
+ gClient.removeListener("closed", onClosed);
+ ok(true, "Client emitted 'closed' event");
+ gClient.close().then(function () {
+ ok(true, "client.close() successfully called its callback");
+ do_test_finished();
+ });
+ };
+ gClient.addListener("closed", onClosed);
+ aTransport.close();
+}
diff --git a/devtools/server/tests/unit/test_client_request.js b/devtools/server/tests/unit/test_client_request.js
new file mode 100644
index 000000000..c0c2c3a92
--- /dev/null
+++ b/devtools/server/tests/unit/test_client_request.js
@@ -0,0 +1,214 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the DebuggerClient.request API.
+
+var gClient, gActorId;
+
+function TestActor(conn) {
+ this.conn = conn;
+}
+TestActor.prototype = {
+ actorPrefix: "test",
+
+ hello: function () {
+ return {hello: "world"};
+ },
+
+ error: function () {
+ return {error: "code", message: "human message"};
+ }
+};
+TestActor.prototype.requestTypes = {
+ "hello": TestActor.prototype.hello,
+ "error": TestActor.prototype.error
+};
+
+function run_test()
+{
+ DebuggerServer.addGlobalActor(TestActor);
+
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ add_test(init);
+ add_test(test_client_request_callback);
+ add_test(test_client_request_promise);
+ add_test(test_client_request_promise_error);
+ add_test(test_client_request_event_emitter);
+ add_test(test_close_client_while_sending_requests);
+ add_test(test_client_request_after_close);
+ add_test(test_client_request_after_close_callback);
+ run_next_test();
+}
+
+function init()
+{
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect()
+ .then(() => gClient.listTabs())
+ .then(aResponse => {
+ gActorId = aResponse.test;
+ run_next_test();
+ });
+}
+
+function checkStack(expectedName) {
+ if (!Services.prefs.getBoolPref("javascript.options.asyncstack")) {
+ do_print("Async stacks are disabled.");
+ return;
+ }
+
+ let stack = Components.stack;
+ while (stack) {
+ do_print(stack.name);
+ if (stack.name == expectedName) {
+ // Reached back to outer function before request
+ ok(true, "Complete stack");
+ return;
+ }
+ stack = stack.asyncCaller || stack.caller;
+ }
+ ok(false, "Incomplete stack");
+}
+
+function test_client_request_callback()
+{
+ // Test that DebuggerClient.request accepts a `onResponse` callback as 2nd argument
+ gClient.request({
+ to: gActorId,
+ type: "hello"
+ }, response => {
+ do_check_eq(response.from, gActorId);
+ do_check_eq(response.hello, "world");
+ checkStack("test_client_request_callback");
+ run_next_test();
+ });
+}
+
+function test_client_request_promise()
+{
+ // Test that DebuggerClient.request returns a promise that resolves on response
+ let request = gClient.request({
+ to: gActorId,
+ type: "hello"
+ });
+
+ request.then(response => {
+ do_check_eq(response.from, gActorId);
+ do_check_eq(response.hello, "world");
+ checkStack("test_client_request_promise");
+ run_next_test();
+ });
+}
+
+function test_client_request_promise_error()
+{
+ // Test that DebuggerClient.request returns a promise that reject when server
+ // returns an explicit error message
+ let request = gClient.request({
+ to: gActorId,
+ type: "error"
+ });
+
+ request.then(() => {
+ do_throw("Promise shouldn't be resolved on error");
+ }, response => {
+ do_check_eq(response.from, gActorId);
+ do_check_eq(response.error, "code");
+ do_check_eq(response.message, "human message");
+ checkStack("test_client_request_promise_error");
+ run_next_test();
+ });
+}
+
+function test_client_request_event_emitter()
+{
+ // Test that DebuggerClient.request returns also an EventEmitter object
+ let request = gClient.request({
+ to: gActorId,
+ type: "hello"
+ });
+ request.on("json-reply", reply => {
+ do_check_eq(reply.from, gActorId);
+ do_check_eq(reply.hello, "world");
+ checkStack("test_client_request_event_emitter");
+ run_next_test();
+ });
+}
+
+function test_close_client_while_sending_requests() {
+ // First send a first request that will be "active"
+ // while the connection is closed.
+ // i.e. will be sent but no response received yet.
+ let activeRequest = gClient.request({
+ to: gActorId,
+ type: "hello"
+ });
+
+ // Pile up a second one that will be "pending".
+ // i.e. won't event be sent.
+ let pendingRequest = gClient.request({
+ to: gActorId,
+ type: "hello"
+ });
+
+ let expectReply = promise.defer();
+ gClient.expectReply("root", function (response) {
+ do_check_eq(response.error, "connectionClosed");
+ do_check_eq(response.message, "server side packet can't be received as the connection just closed.");
+ expectReply.resolve();
+ });
+
+ gClient.close().then(() => {
+ activeRequest.then(() => {
+ ok(false, "First request unexpectedly succeed while closing the connection");
+ }, response => {
+ do_check_eq(response.error, "connectionClosed");
+ do_check_eq(response.message, "'hello' active request packet to '" + gActorId + "' can't be sent as the connection just closed.");
+ })
+ .then(() => pendingRequest)
+ .then(() => {
+ ok(false, "Second request unexpectedly succeed while closing the connection");
+ }, response => {
+ do_check_eq(response.error, "connectionClosed");
+ do_check_eq(response.message, "'hello' pending request packet to '" + gActorId + "' can't be sent as the connection just closed.");
+ })
+ .then(() => expectReply.promise)
+ .then(run_next_test);
+ });
+}
+
+function test_client_request_after_close()
+{
+ // Test that DebuggerClient.request fails after we called client.close()
+ // (with promise API)
+ let request = gClient.request({
+ to: gActorId,
+ type: "hello"
+ });
+
+ request.then(response => {
+ ok(false, "Request succeed even after client.close");
+ }, response => {
+ ok(true, "Request failed after client.close");
+ do_check_eq(response.error, "connectionClosed");
+ ok(response.message.match(/'hello' request packet to '.*' can't be sent as the connection is closed./));
+ run_next_test();
+ });
+}
+
+function test_client_request_after_close_callback()
+{
+ // Test that DebuggerClient.request fails after we called client.close()
+ // (with callback API)
+ let request = gClient.request({
+ to: gActorId,
+ type: "hello"
+ }, response => {
+ ok(true, "Request failed after client.close");
+ do_check_eq(response.error, "connectionClosed");
+ ok(response.message.match(/'hello' request packet to '.*' can't be sent as the connection is closed./));
+ run_next_test();
+ });
+}
diff --git a/devtools/server/tests/unit/test_conditional_breakpoint-01.js b/devtools/server/tests/unit/test_conditional_breakpoint-01.js
new file mode 100644
index 000000000..4661bb0c4
--- /dev/null
+++ b/devtools/server/tests/unit/test_conditional_breakpoint-01.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check conditional breakpoint when condition evaluates to true.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-conditional-breakpoint");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-conditional-breakpoint", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_breakpoint();
+ });
+ });
+ do_test_pending();
+}
+
+function test_simple_breakpoint()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ source.setBreakpoint({
+ line: 3,
+ condition: "a === 1"
+ }, function (aResponse, bpClient) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.why.type, "breakpoint");
+ do_check_eq(aPacket.frame.where.line, 3);
+
+ // Remove the breakpoint.
+ bpClient.remove(function (aResponse) {
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+
+ });
+ // Continue until the breakpoint is hit.
+ gThreadClient.resume();
+
+ });
+
+ });
+
+ Components.utils.evalInSandbox("debugger;\n" + // 1
+ "var a = 1;\n" + // 2
+ "var b = 2;\n", // 3
+ gDebuggee,
+ "1.8",
+ "test.js",
+ 1);
+}
diff --git a/devtools/server/tests/unit/test_conditional_breakpoint-02.js b/devtools/server/tests/unit/test_conditional_breakpoint-02.js
new file mode 100644
index 000000000..873f76159
--- /dev/null
+++ b/devtools/server/tests/unit/test_conditional_breakpoint-02.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check conditional breakpoint when condition evaluates to false.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-conditional-breakpoint");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-conditional-breakpoint", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_breakpoint();
+ });
+ });
+ do_test_pending();
+}
+
+function test_simple_breakpoint()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ source.setBreakpoint({
+ line: 3,
+ condition: "a === 2"
+ }, function (aResponse, bpClient) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.why.type, "debuggerStatement");
+ do_check_eq(aPacket.frame.where.line, 4);
+
+ // Remove the breakpoint.
+ bpClient.remove(function (aResponse) {
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+
+ });
+ // Continue until the breakpoint is hit.
+ gThreadClient.resume();
+ });
+ });
+
+ Components.utils.evalInSandbox("debugger;\n" + // 1
+ "var a = 1;\n" + // 2
+ "var b = 2;\n" + // 3
+ "debugger;", // 4
+ gDebuggee,
+ "1.8",
+ "test.js",
+ 1);
+}
diff --git a/devtools/server/tests/unit/test_conditional_breakpoint-03.js b/devtools/server/tests/unit/test_conditional_breakpoint-03.js
new file mode 100644
index 000000000..d9cf13e00
--- /dev/null
+++ b/devtools/server/tests/unit/test_conditional_breakpoint-03.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check conditional breakpoint when condition throws and make sure it pauses
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-conditional-breakpoint");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-conditional-breakpoint", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_breakpoint();
+ });
+ });
+ do_test_pending();
+}
+
+function test_simple_breakpoint()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let source = gThreadClient.source(aPacket.frame.where.source);
+ source.setBreakpoint({
+ line: 3,
+ condition: "throw new Error()"
+ }, function (aResponse, bpClient) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.why.type, "breakpointConditionThrown");
+ do_check_eq(aPacket.frame.where.line, 3);
+
+ // Remove the breakpoint.
+ bpClient.remove(function (aResponse) {
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+
+ });
+ // Continue until the breakpoint is hit.
+ gThreadClient.resume();
+
+ });
+
+ });
+
+ Components.utils.evalInSandbox("debugger;\n" + // 1
+ "var a = 1;\n" + // 2
+ "var b = 2;\n", // 3
+ gDebuggee,
+ "1.8",
+ "test.js",
+ 1);
+}
diff --git a/devtools/server/tests/unit/test_dbgactor.js b/devtools/server/tests/unit/test_dbgactor.js
new file mode 100644
index 000000000..b22b8446b
--- /dev/null
+++ b/devtools/server/tests/unit/test_dbgactor.js
@@ -0,0 +1,116 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gClient;
+var gDebuggee;
+
+const xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = testGlobal("test-1");
+ DebuggerServer.addTestGlobal(gDebuggee);
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.addListener("connected", function (aEvent, aType, aTraits) {
+ gClient.listTabs((aResponse) => {
+ do_check_true("tabs" in aResponse);
+ for (let tab of aResponse.tabs) {
+ if (tab.title == "test-1") {
+ test_attach_tab(tab.actor);
+ return false;
+ }
+ }
+ do_check_true(false); // We should have found our tab in the list.
+ return undefined;
+ });
+ });
+
+ gClient.connect();
+
+ do_test_pending();
+}
+
+// Attach to |aTabActor|, and check the response.
+function test_attach_tab(aTabActor)
+{
+ gClient.request({ to: aTabActor, type: "attach" }, function (aResponse) {
+ do_check_false("error" in aResponse);
+ do_check_eq(aResponse.from, aTabActor);
+ do_check_eq(aResponse.type, "tabAttached");
+ do_check_true(typeof aResponse.threadActor === "string");
+
+ test_attach_thread(aResponse.threadActor);
+ });
+}
+
+// Attach to |aThreadActor|, check the response, and resume it.
+function test_attach_thread(aThreadActor)
+{
+ gClient.request({ to: aThreadActor, type: "attach" }, function (aResponse) {
+ do_check_false("error" in aResponse);
+ do_check_eq(aResponse.from, aThreadActor);
+ do_check_eq(aResponse.type, "paused");
+ do_check_true("why" in aResponse);
+ do_check_eq(aResponse.why.type, "attached");
+
+ test_resume_thread(aThreadActor);
+ });
+}
+
+// Resume |aThreadActor|, and see that it stops at the 'debugger'
+// statement.
+function test_resume_thread(aThreadActor)
+{
+ // Allow the client to resume execution.
+ gClient.request({ to: aThreadActor, type: "resume" }, function (aResponse) {
+ do_check_false("error" in aResponse);
+ do_check_eq(aResponse.from, aThreadActor);
+ do_check_eq(aResponse.type, "resumed");
+
+ do_check_eq(xpcInspector.eventLoopNestLevel, 0);
+
+ // Now that we know we're resumed, we can make the debuggee do something.
+ Cu.evalInSandbox("var a = true; var b = false; debugger; var b = true;", gDebuggee);
+ // Now make sure that we've run the code after the debugger statement...
+ do_check_true(gDebuggee.b);
+ });
+
+ gClient.addListener("paused", function (aName, aPacket) {
+ do_check_eq(aName, "paused");
+ do_check_false("error" in aPacket);
+ do_check_eq(aPacket.from, aThreadActor);
+ do_check_eq(aPacket.type, "paused");
+ do_check_true("actor" in aPacket);
+ do_check_true("why" in aPacket);
+ do_check_eq(aPacket.why.type, "debuggerStatement");
+
+ // Reach around the protocol to check that the debuggee is in the state
+ // we expect.
+ do_check_true(gDebuggee.a);
+ do_check_false(gDebuggee.b);
+
+ do_check_eq(xpcInspector.eventLoopNestLevel, 1);
+
+ // Let the debuggee continue execution.
+ gClient.request({ to: aThreadActor, type: "resume" }, cleanup);
+ });
+}
+
+function cleanup()
+{
+ gClient.addListener("closed", function (aEvent, aResult) {
+ do_test_finished();
+ });
+
+ try {
+ let xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
+ do_check_eq(xpcInspector.eventLoopNestLevel, 0);
+ } catch (e) {
+ dump(e);
+ }
+
+ gClient.close();
+}
diff --git a/devtools/server/tests/unit/test_dbgclient_debuggerstatement.js b/devtools/server/tests/unit/test_dbgclient_debuggerstatement.js
new file mode 100644
index 000000000..40468cb1d
--- /dev/null
+++ b/devtools/server/tests/unit/test_dbgclient_debuggerstatement.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gClient;
+var gTabClient;
+var gDebuggee;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = testGlobal("test-1");
+ DebuggerServer.addTestGlobal(gDebuggee);
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(function ([aType, aTraits]) {
+ attachTestTab(gClient, "test-1", function (aReply, aTabClient) {
+ gTabClient = aTabClient;
+ test_threadAttach(aReply.threadActor);
+ });
+ });
+ do_test_pending();
+}
+
+function test_threadAttach(aThreadActorID)
+{
+ do_print("Trying to attach to thread " + aThreadActorID);
+ gTabClient.attachThread({}, function (aResponse, aThreadClient) {
+ do_check_eq(aThreadClient.state, "paused");
+ do_check_eq(aThreadClient.actor, aThreadActorID);
+ aThreadClient.resume(function () {
+ do_check_eq(aThreadClient.state, "attached");
+ test_debugger_statement(aThreadClient);
+ });
+ });
+}
+
+function test_debugger_statement(aThreadClient)
+{
+ aThreadClient.addListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aThreadClient.state, "paused");
+ // Reach around the protocol to check that the debuggee is in the state
+ // we expect.
+ do_check_true(gDebuggee.a);
+ do_check_false(gDebuggee.b);
+
+ let xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
+ do_check_eq(xpcInspector.eventLoopNestLevel, 1);
+
+ aThreadClient.resume(cleanup);
+ });
+
+ Cu.evalInSandbox("var a = true; var b = false; debugger; var b = true;", gDebuggee);
+
+ // Now make sure that we've run the code after the debugger statement...
+ do_check_true(gDebuggee.b);
+}
+
+function cleanup()
+{
+ gClient.addListener("closed", function (aEvent) {
+ do_test_finished();
+ });
+
+ try {
+ let xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
+ do_check_eq(xpcInspector.eventLoopNestLevel, 0);
+ } catch (e) {
+ dump(e);
+ }
+
+ gClient.close();
+}
diff --git a/devtools/server/tests/unit/test_dbgglobal.js b/devtools/server/tests/unit/test_dbgglobal.js
new file mode 100644
index 000000000..ff4291932
--- /dev/null
+++ b/devtools/server/tests/unit/test_dbgglobal.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test()
+{
+ // Should get an exception if we try to interact with DebuggerServer
+ // before we initialize it...
+ check_except(function () {
+ DebuggerServer.createListener();
+ });
+ check_except(DebuggerServer.closeAllListeners);
+ check_except(DebuggerServer.connectPipe);
+
+ // Allow incoming connections.
+ DebuggerServer.init();
+
+ // These should still fail because we haven't added a createRootActor
+ // implementation yet.
+ check_except(function () {
+ DebuggerServer.createListener();
+ });
+ check_except(DebuggerServer.closeAllListeners);
+ check_except(DebuggerServer.connectPipe);
+
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+
+ // Now they should work.
+ DebuggerServer.createListener();
+ DebuggerServer.closeAllListeners();
+
+ // Make sure we got the test's root actor all set up.
+ let client1 = DebuggerServer.connectPipe();
+ client1.hooks = {
+ onPacket: function (aPacket1) {
+ do_check_eq(aPacket1.from, "root");
+ do_check_eq(aPacket1.applicationType, "xpcshell-tests");
+
+ // Spin up a second connection, make sure it has its own root
+ // actor.
+ let client2 = DebuggerServer.connectPipe();
+ client2.hooks = {
+ onPacket: function (aPacket2) {
+ do_check_eq(aPacket2.from, "root");
+ do_check_neq(aPacket1.testConnectionPrefix,
+ aPacket2.testConnectionPrefix);
+ client2.close();
+ },
+ onClosed: function (aResult) {
+ client1.close();
+ },
+ };
+ client2.ready();
+ },
+
+ onClosed: function (aResult) {
+ do_test_finished();
+ },
+ };
+
+ client1.ready();
+ do_test_pending();
+}
diff --git a/devtools/server/tests/unit/test_eval-01.js b/devtools/server/tests/unit/test_eval-01.js
new file mode 100644
index 000000000..b11903f87
--- /dev/null
+++ b/devtools/server/tests/unit/test_eval-01.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check basic eval resume/re-pause
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_eval();
+ });
+ });
+ do_test_pending();
+}
+
+function test_simple_eval()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let arg1Actor = aPacket.frame.arguments[0].actor;
+ gThreadClient.eval(null, "({ obj: true })", function (aResponse) {
+ do_check_eq(aResponse.type, "resumed");
+ // Expect a pause notification immediately.
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value...
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.why.type, "clientEvaluated");
+ do_check_eq(aPacket.why.frameFinished.return.type, "object");
+ do_check_eq(aPacket.why.frameFinished.return.class, "Object");
+
+ // Make sure the previous pause lifetime was correctly dropped.
+ gClient.request({ to: arg1Actor, type: "bogusRequest" }, function (aResponse) {
+ do_check_eq(aResponse.error, "noSuchActor");
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+
+ });
+
+ });
+
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(arg1) { debugger; }
+ stopMe({obj: true});
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_eval-02.js b/devtools/server/tests/unit/test_eval-02.js
new file mode 100644
index 000000000..386ea5c98
--- /dev/null
+++ b/devtools/server/tests/unit/test_eval-02.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check eval resume/re-pause with a throw.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_throw_eval();
+ });
+ });
+ do_test_pending();
+}
+
+function test_throw_eval()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.eval(null, "throw 'failure'", function (aResponse) {
+ do_check_eq(aResponse.type, "resumed");
+ // Expect a pause notification immediately.
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value...
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.why.type, "clientEvaluated");
+ do_check_eq(aPacket.why.frameFinished.throw, "failure");
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(arg1) { debugger; }
+ stopMe({obj: true});
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_eval-03.js b/devtools/server/tests/unit/test_eval-03.js
new file mode 100644
index 000000000..2234259aa
--- /dev/null
+++ b/devtools/server/tests/unit/test_eval-03.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check syntax errors in an eval.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_syntax_error_eval();
+ });
+ });
+ do_test_pending();
+}
+
+function test_syntax_error_eval()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.eval(null, "%$@!@#", function (aResponse) {
+ do_check_eq(aResponse.type, "resumed");
+ // Expect a pause notification immediately.
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value...
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.why.type, "clientEvaluated");
+ do_check_eq(aPacket.why.frameFinished.throw.type, "object");
+ do_check_eq(aPacket.why.frameFinished.throw.class, "Error");
+
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(arg1) { debugger; }
+ stopMe({obj: true});
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_eval-04.js b/devtools/server/tests/unit/test_eval-04.js
new file mode 100644
index 000000000..77cb58d97
--- /dev/null
+++ b/devtools/server/tests/unit/test_eval-04.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check evals against different frames.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_syntax_error_eval();
+ });
+ });
+ do_test_pending();
+}
+
+function test_syntax_error_eval()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+
+ gThreadClient.getFrames(0, 2, function (aResponse) {
+ let frame0 = aResponse.frames[0];
+ let frame1 = aResponse.frames[1];
+
+ // Eval against the top frame...
+ gThreadClient.eval(frame0.actor, "arg", function (aResponse) {
+ do_check_eq(aResponse.type, "resumed");
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // 'arg' should have been evaluated in frame0
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.why.type, "clientEvaluated");
+ do_check_eq(aPacket.why.frameFinished.return, "arg0");
+
+ // Now eval against the second frame.
+ gThreadClient.eval(frame1.actor, "arg", function (aResponse) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // 'arg' should have been evaluated in frame1
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.why.frameFinished.return, "arg1");
+
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function frame0(arg) {
+ debugger;
+ }
+ function frame1(arg) {
+ frame0("arg0");
+ }
+ frame1("arg1");
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_eval-05.js b/devtools/server/tests/unit/test_eval-05.js
new file mode 100644
index 000000000..b199e4afb
--- /dev/null
+++ b/devtools/server/tests/unit/test_eval-05.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check pauses within evals.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_syntax_error_eval();
+ });
+ });
+ do_test_pending();
+}
+
+function test_syntax_error_eval()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.eval(null, "debugger", function (aResponse) {
+ // Expect a resume then a debuggerStatement pause.
+ do_check_eq(aResponse.type, "resumed");
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aPacket.why.type, "debuggerStatement");
+ // Resume from the debugger statement should immediately re-pause
+ // with a clientEvaluated reason.
+ gThreadClient.resume(function (aPacket) {
+ do_check_eq(aPacket.type, "resumed");
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aPacket.why.type, "clientEvaluated");
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+ });
+ });
+ });
+ gDebuggee.eval("(" + function () {
+ function stopMe(arg) {
+ debugger;
+ }
+ stopMe();
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_eventlooplag_actor.js b/devtools/server/tests/unit/test_eventlooplag_actor.js
new file mode 100644
index 000000000..d2acdd8f8
--- /dev/null
+++ b/devtools/server/tests/unit/test_eventlooplag_actor.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test the eventLoopLag actor.
+ */
+
+"use strict";
+
+function run_test()
+{
+ let {EventLoopLagFront} = require("devtools/shared/fronts/eventlooplag");
+
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ // As seen in EventTracer.cpp
+ let threshold = 20;
+ let interval = 10;
+
+
+ let front;
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+
+ // Start tracking event loop lags.
+ client.connect().then(function () {
+ client.listTabs(function (resp) {
+ front = new EventLoopLagFront(client, resp);
+ front.start().then(success => {
+ do_check_true(success);
+ front.once("event-loop-lag", gotLagEvent);
+ do_execute_soon(lag);
+ });
+ });
+ });
+
+ // Force a lag
+ function lag() {
+ let start = new Date();
+ let duration = threshold + interval + 1;
+ while (true) {
+ if (((new Date()) - start) > duration) {
+ break;
+ }
+ }
+ }
+
+ // Got a lag event. The test will time out if the actor
+ // fails to detect the lag.
+ function gotLagEvent(time) {
+ do_print("lag: " + time);
+ do_check_true(time >= threshold);
+ front.stop().then(() => {
+ finishClient(client);
+ });
+ }
+
+ do_test_pending();
+}
diff --git a/devtools/server/tests/unit/test_forwardingprefix.js b/devtools/server/tests/unit/test_forwardingprefix.js
new file mode 100644
index 000000000..885a99db8
--- /dev/null
+++ b/devtools/server/tests/unit/test_forwardingprefix.js
@@ -0,0 +1,196 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Exercise prefix-based forwarding of packets to other transports. */
+
+const { RootActor } = require("devtools/server/actors/root");
+
+var gMainConnection, gMainTransport;
+var gSubconnection1, gSubconnection2;
+var gClient;
+
+function run_test()
+{
+ DebuggerServer.init();
+
+ add_test(createMainConnection);
+ add_test(TestNoForwardingYet);
+ add_test(createSubconnection1);
+ add_test(TestForwardPrefix1OnlyRoot);
+ add_test(createSubconnection2);
+ add_test(TestForwardPrefix12OnlyRoot);
+ add_test(TestForwardPrefix12WithActor1);
+ add_test(TestForwardPrefix12WithActor12);
+ run_next_test();
+}
+
+/*
+ * Create a pipe connection, and return an object |{ conn, transport }|,
+ * where |conn| is the new DebuggerServerConnection instance, and
+ * |transport| is the client side of the transport on which it communicates
+ * (that is, packets sent on |transport| go to the new connection, and
+ * |transport|'s hooks receive replies).
+ *
+ * |aPrefix| is optional; if present, it's the prefix (minus the '/') for
+ * actors in the new connection.
+ */
+function newConnection(aPrefix)
+{
+ var conn;
+ DebuggerServer.createRootActor = function (aConn) {
+ conn = aConn;
+ return new RootActor(aConn, {});
+ };
+
+ var transport = DebuggerServer.connectPipe(aPrefix);
+
+ return { conn: conn, transport: transport };
+}
+
+/* Create the main connection for these tests. */
+function createMainConnection()
+{
+ ({ conn: gMainConnection, transport: gMainTransport } = newConnection());
+ gClient = new DebuggerClient(gMainTransport);
+ gClient.connect().then(([aType, aTraits]) => run_next_test());
+}
+
+/*
+ * Exchange 'echo' messages with five actors:
+ * - root
+ * - prefix1/root
+ * - prefix1/actor
+ * - prefix2/root
+ * - prefix2/actor
+ *
+ * Expect proper echos from those named in |aReachables|, and 'noSuchActor'
+ * errors from the others. When we've gotten all our replies (errors or
+ * otherwise), call |aCompleted|.
+ *
+ * To avoid deep stacks, we call aCompleted from the next tick.
+ */
+function tryActors(aReachables, aCompleted) {
+ let count = 0;
+
+ let outerActor;
+ for (outerActor of [ "root",
+ "prefix1/root", "prefix1/actor",
+ "prefix2/root", "prefix2/actor" ]) {
+ /*
+ * Let each callback capture its own iteration's value; outerActor is
+ * local to the whole loop, not to a single iteration.
+ */
+ let actor = outerActor;
+
+ count++;
+
+ gClient.request({ to: actor, type: "echo", value: "tango"}, // phone home
+ (aResponse) => {
+ if (aReachables.has(actor))
+ do_check_matches({ from: actor, to: actor, type: "echo", value: "tango" }, aResponse);
+ else
+ do_check_matches({ from: actor, error: "noSuchActor", message: "No such actor for ID: " + actor }, aResponse);
+
+ if (--count == 0)
+ do_execute_soon(aCompleted, "tryActors callback " + aCompleted.name);
+ });
+ }
+}
+
+/*
+ * With no forwarding established, sending messages to root should work,
+ * but sending messages to prefixed actor names, or anyone else, should get
+ * an error.
+ */
+function TestNoForwardingYet()
+{
+ tryActors(new Set(["root"]), run_next_test);
+}
+
+/*
+ * Create a new pipe connection which forwards its reply packets to
+ * gMainConnection's client, and to which gMainConnection forwards packets
+ * directed to actors whose names begin with |aPrefix + '/'|, and.
+ *
+ * Return an object { conn, transport }, as for newConnection.
+ */
+function newSubconnection(aPrefix)
+{
+ let { conn, transport } = newConnection(aPrefix);
+ transport.hooks = {
+ onPacket: (aPacket) => gMainConnection.send(aPacket),
+ onClosed: () => {}
+ };
+ gMainConnection.setForwarding(aPrefix, transport);
+
+ return { conn: conn, transport: transport };
+}
+
+/* Create a second root actor, to which we can forward things. */
+function createSubconnection1()
+{
+ let { conn, transport } = newSubconnection("prefix1");
+ gSubconnection1 = conn;
+ transport.ready();
+ gClient.expectReply("prefix1/root", (aReply) => run_next_test());
+}
+
+// Establish forwarding, but don't put any actors in that server.
+function TestForwardPrefix1OnlyRoot()
+{
+ tryActors(new Set(["root", "prefix1/root"]), run_next_test);
+}
+
+/* Create a third root actor, to which we can forward things. */
+function createSubconnection2()
+{
+ let { conn, transport } = newSubconnection("prefix2");
+ gSubconnection2 = conn;
+ transport.ready();
+ gClient.expectReply("prefix2/root", (aReply) => run_next_test());
+}
+
+function TestForwardPrefix12OnlyRoot()
+{
+ tryActors(new Set(["root", "prefix1/root", "prefix2/root"]), run_next_test);
+}
+
+// A dumb actor that implements 'echo'.
+//
+// It's okay that both subconnections' actors behave identically, because
+// the reply-sending code attaches the replying actor's name to the packet,
+// so simply matching the 'from' field in the reply ensures that we heard
+// from the right actor.
+function EchoActor(aConnection)
+{
+ this.conn = aConnection;
+}
+EchoActor.prototype.actorPrefix = "EchoActor";
+EchoActor.prototype.onEcho = function (aRequest) {
+ /*
+ * Request packets are frozen. Copy aRequest, so that
+ * DebuggerServerConnection.onPacket can attach a 'from' property.
+ */
+ return JSON.parse(JSON.stringify(aRequest));
+};
+EchoActor.prototype.requestTypes = {
+ "echo": EchoActor.prototype.onEcho
+};
+
+function TestForwardPrefix12WithActor1()
+{
+ let actor = new EchoActor(gSubconnection1);
+ actor.actorID = "prefix1/actor";
+ gSubconnection1.addActor(actor);
+
+ tryActors(new Set(["root", "prefix1/root", "prefix1/actor", "prefix2/root"]), run_next_test);
+}
+
+function TestForwardPrefix12WithActor12()
+{
+ let actor = new EchoActor(gSubconnection2);
+ actor.actorID = "prefix2/actor";
+ gSubconnection2.addActor(actor);
+
+ tryActors(new Set(["root", "prefix1/root", "prefix1/actor", "prefix2/root", "prefix2/actor"]), run_next_test);
+}
diff --git a/devtools/server/tests/unit/test_frameactor-01.js b/devtools/server/tests/unit/test_frameactor-01.js
new file mode 100644
index 000000000..ad37a8ab5
--- /dev/null
+++ b/devtools/server/tests/unit/test_frameactor-01.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Verify that we get a frame actor along with a debugger statement.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_true(!!aPacket.frame);
+ do_check_true(!!aPacket.frame.actor);
+ do_check_eq(aPacket.frame.callee.name, "stopMe");
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe() {
+ debugger;
+ }
+ stopMe();
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_frameactor-02.js b/devtools/server/tests/unit/test_frameactor-02.js
new file mode 100644
index 000000000..f2890adac
--- /dev/null
+++ b/devtools/server/tests/unit/test_frameactor-02.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Verify that two pauses in a row will keep the same frame actor.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket1) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket2) {
+ do_check_eq(aPacket1.frame.actor, aPacket2.frame.actor);
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ gThreadClient.resume();
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe() {
+ debugger;
+ debugger;
+ }
+ stopMe();
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_frameactor-03.js b/devtools/server/tests/unit/test_frameactor-03.js
new file mode 100644
index 000000000..0d7739d5a
--- /dev/null
+++ b/devtools/server/tests/unit/test_frameactor-03.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Verify that a frame actor is properly expired when the frame goes away.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket1) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket2) {
+ let poppedFrames = aPacket2.poppedFrames;
+ do_check_eq(typeof (poppedFrames), typeof ([]));
+ do_check_true(poppedFrames.indexOf(aPacket1.frame.actor) >= 0);
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ gThreadClient.resume();
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe() {
+ debugger;
+ }
+ stopMe();
+ debugger;
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_frameactor-04.js b/devtools/server/tests/unit/test_frameactor-04.js
new file mode 100644
index 000000000..b4faa96e0
--- /dev/null
+++ b/devtools/server/tests/unit/test_frameactor-04.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Verify the "frames" request on the thread.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+var gFrames = [
+ // Function calls...
+ { type: "call", callee: { name: "depth3" } },
+ { type: "call", callee: { name: "depth2" } },
+ { type: "call", callee: { name: "depth1" } },
+
+ // Anonymous function call in our eval...
+ { type: "call", callee: { name: undefined } },
+
+ // The eval itself.
+ { type: "eval", callee: { name: undefined } },
+];
+
+var gSliceTests = [
+ { start: 0, count: undefined, resetActors: true },
+ { start: 0, count: 1 },
+ { start: 2, count: 2 },
+ { start: 1, count: 15 },
+ { start: 15, count: undefined },
+];
+
+function test_frame_slice() {
+ if (gSliceTests.length == 0) {
+ gThreadClient.resume(function () { finishClient(gClient); });
+ return;
+ }
+
+ let test = gSliceTests.shift();
+ gThreadClient.getFrames(test.start, test.count, function (aResponse) {
+ var testFrames = gFrames.slice(test.start, test.count ? test.start + test.count : undefined);
+ do_check_eq(testFrames.length, aResponse.frames.length);
+ for (var i = 0; i < testFrames.length; i++) {
+ let expected = testFrames[i];
+ let actual = aResponse.frames[i];
+
+ if (test.resetActors) {
+ expected.actor = actual.actor;
+ }
+
+ for (let key of ["type", "callee-name"]) {
+ do_check_eq(expected[key] || undefined, actual[key]);
+ }
+ }
+ test_frame_slice();
+ });
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket1) {
+ test_frame_slice();
+ });
+
+ gDebuggee.eval("(" + function () {
+ function depth3() {
+ debugger;
+ }
+ function depth2() {
+ depth3();
+ }
+ function depth1() {
+ depth2();
+ }
+ depth1();
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_frameactor-05.js b/devtools/server/tests/unit/test_frameactor-05.js
new file mode 100644
index 000000000..feece598e
--- /dev/null
+++ b/devtools/server/tests/unit/test_frameactor-05.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Verify that frame actors retrieved with the frames request
+ * are included in the pause packet's popped-frames property.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_frame_slice() {
+ if (gSliceTests.length == 0) {
+ gThreadClient.resume(function () { finishClient(gClient); });
+ return;
+ }
+
+ let test = gSliceTests.shift();
+ gThreadClient.getFrames(test.start, test.count, function (aResponse) {
+ var testFrames = gFrames.slice(test.start, test.count ? test.start + test.count : undefined);
+ do_check_eq(testFrames.length, aResponse.frames.length);
+ for (var i = 0; i < testFrames.length; i++) {
+ let expected = testFrames[i];
+ let actual = aResponse.frames[i];
+
+ if (test.resetActors) {
+ expected.actor = actual.actor;
+ }
+
+ for (var key in expected) {
+ do_check_eq(expected[key], actual[key]);
+ }
+ }
+ test_frame_slice();
+ });
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket1) {
+ gThreadClient.getFrames(0, null, function (aFrameResponse) {
+ do_check_eq(aFrameResponse.frames.length, 5);
+ // Now wait for the next pause, after which the three
+ // youngest actors should be popped..
+ let expectPopped = aFrameResponse.frames.slice(0, 3).map(frame => frame.actor);
+ expectPopped.sort();
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPausePacket) {
+ let popped = aPausePacket.poppedFrames.sort();
+ do_check_eq(popped.length, 3);
+ for (let i = 0; i < 3; i++) {
+ do_check_eq(expectPopped[i], popped[i]);
+ }
+
+ gThreadClient.resume(function () { finishClient(gClient); });
+ });
+ gThreadClient.resume();
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function depth3() {
+ debugger;
+ }
+ function depth2() {
+ depth3();
+ }
+ function depth1() {
+ depth2();
+ }
+ depth1();
+ debugger;
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_framearguments-01.js b/devtools/server/tests/unit/test_framearguments-01.js
new file mode 100644
index 000000000..e075d2c25
--- /dev/null
+++ b/devtools/server/tests/unit/test_framearguments-01.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check a frame actor's arguments property.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame["arguments"];
+ do_check_eq(args.length, 6);
+ do_check_eq(args[0], 42);
+ do_check_eq(args[1], true);
+ do_check_eq(args[2], "nasu");
+ do_check_eq(args[3].type, "null");
+ do_check_eq(args[4].type, "undefined");
+ do_check_eq(args[5].type, "object");
+ do_check_eq(args[5].class, "Object");
+ do_check_true(!!args[5].actor);
+
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(aNumber, aBool, aString, aNull, aUndefined, aObject) {
+ debugger;
+ }
+ stopMe(42, true, "nasu", null, undefined, { foo: "bar" });
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_framebindings-01.js b/devtools/server/tests/unit/test_framebindings-01.js
new file mode 100644
index 000000000..ae62f8ff1
--- /dev/null
+++ b/devtools/server/tests/unit/test_framebindings-01.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check a frame actor's bindings property.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let bindings = aPacket.frame.environment.bindings;
+ let args = bindings.arguments;
+ let vars = bindings.variables;
+
+ do_check_eq(args.length, 6);
+ do_check_eq(args[0].aNumber.value, 42);
+ do_check_eq(args[1].aBool.value, true);
+ do_check_eq(args[2].aString.value, "nasu");
+ do_check_eq(args[3].aNull.value.type, "null");
+ do_check_eq(args[4].aUndefined.value.type, "undefined");
+ do_check_eq(args[5].aObject.value.type, "object");
+ do_check_eq(args[5].aObject.value.class, "Object");
+ do_check_true(!!args[5].aObject.value.actor);
+
+ do_check_eq(vars.a.value, 1);
+ do_check_eq(vars.b.value, true);
+ do_check_eq(vars.c.value.type, "object");
+ do_check_eq(vars.c.value.class, "Object");
+ do_check_true(!!vars.c.value.actor);
+
+ let objClient = gThreadClient.pauseGrip(vars.c.value);
+ objClient.getPrototypeAndProperties(function (aResponse) {
+ do_check_eq(aResponse.ownProperties.a.configurable, true);
+ do_check_eq(aResponse.ownProperties.a.enumerable, true);
+ do_check_eq(aResponse.ownProperties.a.writable, true);
+ do_check_eq(aResponse.ownProperties.a.value, "a");
+
+ do_check_eq(aResponse.ownProperties.b.configurable, true);
+ do_check_eq(aResponse.ownProperties.b.enumerable, true);
+ do_check_eq(aResponse.ownProperties.b.writable, true);
+ do_check_eq(aResponse.ownProperties.b.value.type, "undefined");
+ do_check_false("class" in aResponse.ownProperties.b.value);
+
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(aNumber, aBool, aString, aNull, aUndefined, aObject) {
+ var a = 1;
+ var b = true;
+ var c = { a: "a", b: undefined };
+ debugger;
+ }
+ stopMe(42, true, "nasu", null, undefined, { foo: "bar" });
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_framebindings-02.js b/devtools/server/tests/unit/test_framebindings-02.js
new file mode 100644
index 000000000..552670349
--- /dev/null
+++ b/devtools/server/tests/unit/test_framebindings-02.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check a frame actor's parent bindings.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let parentEnv = aPacket.frame.environment.parent;
+ let bindings = parentEnv.bindings;
+ let args = bindings.arguments;
+ let vars = bindings.variables;
+ do_check_neq(parentEnv, undefined);
+ do_check_eq(args.length, 0);
+ do_check_eq(vars.stopMe.value.type, "object");
+ do_check_eq(vars.stopMe.value.class, "Function");
+ do_check_true(!!vars.stopMe.value.actor);
+
+ // Skip the global lexical scope.
+ parentEnv = parentEnv.parent.parent;
+ do_check_neq(parentEnv, undefined);
+ let objClient = gThreadClient.pauseGrip(parentEnv.object);
+ objClient.getPrototypeAndProperties(function (aResponse) {
+ do_check_eq(aResponse.ownProperties.Object.value.type, "object");
+ do_check_eq(aResponse.ownProperties.Object.value.class, "Function");
+ do_check_true(!!aResponse.ownProperties.Object.value.actor);
+
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(aNumber, aBool, aString, aNull, aUndefined, aObject) {
+ var a = 1;
+ var b = true;
+ var c = { a: "a" };
+ debugger;
+ }
+ stopMe(42, true, "nasu", null, undefined, { foo: "bar" });
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_framebindings-03.js b/devtools/server/tests/unit/test_framebindings-03.js
new file mode 100644
index 000000000..1ec51570d
--- /dev/null
+++ b/devtools/server/tests/unit/test_framebindings-03.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check a |with| frame actor's bindings.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let env = aPacket.frame.environment;
+ do_check_neq(env, undefined);
+
+ let parentEnv = env.parent;
+ do_check_neq(parentEnv, undefined);
+
+ let bindings = parentEnv.bindings;
+ let args = bindings.arguments;
+ let vars = bindings.variables;
+ do_check_eq(args.length, 1);
+ do_check_eq(args[0].aNumber.value, 10);
+ do_check_eq(vars.r.value, 10);
+ do_check_eq(vars.a.value, Math.PI * 100);
+ do_check_eq(vars.arguments.value.class, "Arguments");
+ do_check_true(!!vars.arguments.value.actor);
+
+ let objClient = gThreadClient.pauseGrip(env.object);
+ objClient.getPrototypeAndProperties(function (aResponse) {
+ do_check_eq(aResponse.ownProperties.PI.value, Math.PI);
+ do_check_eq(aResponse.ownProperties.cos.value.type, "object");
+ do_check_eq(aResponse.ownProperties.cos.value.class, "Function");
+ do_check_true(!!aResponse.ownProperties.cos.value.actor);
+
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(aNumber) {
+ var a;
+ var r = aNumber;
+ with (Math) {
+ a = PI * r * r;
+ debugger;
+ }
+ }
+ stopMe(10);
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_framebindings-04.js b/devtools/server/tests/unit/test_framebindings-04.js
new file mode 100644
index 000000000..963a12055
--- /dev/null
+++ b/devtools/server/tests/unit/test_framebindings-04.js
@@ -0,0 +1,84 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check the environment bindongs of a |with| within a |with|.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let env = aPacket.frame.environment;
+ do_check_neq(env, undefined);
+
+ let objClient = gThreadClient.pauseGrip(env.object);
+ objClient.getPrototypeAndProperties(function (aResponse) {
+ do_check_eq(aResponse.ownProperties.one.value, 1);
+ do_check_eq(aResponse.ownProperties.two.value, 2);
+ do_check_eq(aResponse.ownProperties.foo, undefined);
+
+ let parentEnv = env.parent;
+ do_check_neq(parentEnv, undefined);
+
+ let parentClient = gThreadClient.pauseGrip(parentEnv.object);
+ parentClient.getPrototypeAndProperties(function (aResponse) {
+ do_check_eq(aResponse.ownProperties.PI.value, Math.PI);
+ do_check_eq(aResponse.ownProperties.cos.value.type, "object");
+ do_check_eq(aResponse.ownProperties.cos.value.class, "Function");
+ do_check_true(!!aResponse.ownProperties.cos.value.actor);
+
+ parentEnv = parentEnv.parent;
+ do_check_neq(parentEnv, undefined);
+
+ let bindings = parentEnv.bindings;
+ let args = bindings.arguments;
+ let vars = bindings.variables;
+ do_check_eq(args.length, 1);
+ do_check_eq(args[0].aNumber.value, 10);
+ do_check_eq(vars.r.value, 10);
+ do_check_eq(vars.a.value, Math.PI * 100);
+ do_check_eq(vars.arguments.value.class, "Arguments");
+ do_check_true(!!vars.arguments.value.actor);
+ do_check_eq(vars.foo.value, 2 * Math.PI);
+
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(aNumber) {
+ var a, obj = { one: 1, two: 2 };
+ var r = aNumber;
+ with (Math) {
+ a = PI * r * r;
+ with (obj) {
+ var foo = two * PI;
+ debugger;
+ }
+ }
+ }
+ stopMe(10);
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_framebindings-05.js b/devtools/server/tests/unit/test_framebindings-05.js
new file mode 100644
index 000000000..9827c617a
--- /dev/null
+++ b/devtools/server/tests/unit/test_framebindings-05.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check the environment bindings of a |with| in global scope.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let env = aPacket.frame.environment;
+ do_check_neq(env, undefined);
+
+ let objClient = gThreadClient.pauseGrip(env.object);
+ objClient.getPrototypeAndProperties(function (aResponse) {
+ do_check_eq(aResponse.ownProperties.PI.value, Math.PI);
+ do_check_eq(aResponse.ownProperties.cos.value.type, "object");
+ do_check_eq(aResponse.ownProperties.cos.value.class, "Function");
+ do_check_true(!!aResponse.ownProperties.cos.value.actor);
+
+ // Skip the global lexical scope.
+ let parentEnv = env.parent.parent;
+ do_check_neq(parentEnv, undefined);
+
+ let parentClient = gThreadClient.pauseGrip(parentEnv.object);
+ parentClient.getPrototypeAndProperties(function (aResponse) {
+ do_check_eq(aResponse.ownProperties.a.value, Math.PI * 100);
+ do_check_eq(aResponse.ownProperties.r.value, 10);
+ do_check_eq(aResponse.ownProperties.Object.value.type, "object");
+ do_check_eq(aResponse.ownProperties.Object.value.class, "Function");
+ do_check_true(!!aResponse.ownProperties.Object.value.actor);
+
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+ });
+
+ gDebuggee.eval("var a, r = 10;\n" +
+ "with (Math) {\n" +
+ " a = PI * r * r;\n" +
+ " debugger;\n" +
+ "}");
+}
diff --git a/devtools/server/tests/unit/test_framebindings-06.js b/devtools/server/tests/unit/test_framebindings-06.js
new file mode 100644
index 000000000..9d8478a29
--- /dev/null
+++ b/devtools/server/tests/unit/test_framebindings-06.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_banana_environment();
+ });
+ });
+ do_test_pending();
+}
+
+function test_banana_environment()
+{
+
+ gThreadClient.addOneTimeListener("paused",
+ function (aEvent, aPacket) {
+ equal(aPacket.type, "paused");
+ let env = aPacket.frame.environment;
+ equal(env.type, "function");
+ equal(env.function.name, "banana3");
+ let parent = env.parent;
+ equal(parent.type, "block");
+ ok("banana3" in parent.bindings.variables);
+ parent = parent.parent;
+ equal(parent.type, "function");
+ equal(parent.function.name, "banana2");
+ parent = parent.parent;
+ equal(parent.type, "block");
+ ok("banana2" in parent.bindings.variables);
+ parent = parent.parent;
+ equal(parent.type, "function");
+ equal(parent.function.name, "banana");
+
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+
+ gDebuggee.eval("\
+ function banana(x) { \n\
+ return function banana2(y) { \n\
+ return function banana3(z) { \n\
+ debugger; \n\
+ }; \n\
+ }; \n\
+ } \n\
+ banana('x')('y')('z'); \n\
+ ");
+}
diff --git a/devtools/server/tests/unit/test_framebindings-07.js b/devtools/server/tests/unit/test_framebindings-07.js
new file mode 100644
index 000000000..bdfc36d97
--- /dev/null
+++ b/devtools/server/tests/unit/test_framebindings-07.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+// Test that the EnvironmentClient's getBindings() method works as expected.
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-bindings");
+
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-bindings", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_banana_environment();
+ });
+ });
+ do_test_pending();
+}
+
+function test_banana_environment()
+{
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let environment = aPacket.frame.environment;
+ do_check_eq(environment.type, "function");
+
+ let parent = environment.parent;
+ do_check_eq(parent.type, "block");
+
+ let grandpa = parent.parent;
+ do_check_eq(grandpa.type, "function");
+
+ let envClient = gThreadClient.environment(environment);
+ envClient.getBindings(aResponse => {
+ do_check_eq(aResponse.bindings.arguments[0].z.value, "z");
+
+ let parentClient = gThreadClient.environment(parent);
+ parentClient.getBindings(aResponse => {
+ do_check_eq(aResponse.bindings.variables.banana3.value.class, "Function");
+
+ let grandpaClient = gThreadClient.environment(grandpa);
+ grandpaClient.getBindings(aResponse => {
+ do_check_eq(aResponse.bindings.arguments[0].y.value, "y");
+ gThreadClient.resume(() => finishClient(gClient));
+ });
+ });
+ });
+ });
+
+ gDebuggee.eval("\
+ function banana(x) { \n\
+ return function banana2(y) { \n\
+ return function banana3(z) { \n\
+ debugger; \n\
+ }; \n\
+ }; \n\
+ } \n\
+ banana('x')('y')('z'); \n\
+ ");
+}
diff --git a/devtools/server/tests/unit/test_frameclient-01.js b/devtools/server/tests/unit/test_frameclient-01.js
new file mode 100644
index 000000000..a441c9ade
--- /dev/null
+++ b/devtools/server/tests/unit/test_frameclient-01.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.addOneTimeListener("framesadded", function () {
+ do_check_eq(gThreadClient.cachedFrames.length, 3);
+ do_check_true(gThreadClient.moreFrames);
+ do_check_false(gThreadClient.fillFrames(3));
+
+ do_check_true(gThreadClient.fillFrames(30));
+ gThreadClient.addOneTimeListener("framesadded", function () {
+ do_check_false(gThreadClient.moreFrames);
+ do_check_eq(gThreadClient.cachedFrames.length, 7);
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+ do_check_true(gThreadClient.fillFrames(3));
+ });
+
+ gDebuggee.eval("(" + function () {
+ var recurseLeft = 5;
+ function recurse() {
+ if (--recurseLeft == 0) {
+ debugger;
+ return;
+ }
+ recurse();
+ }
+ recurse();
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_frameclient-02.js b/devtools/server/tests/unit/test_frameclient-02.js
new file mode 100644
index 000000000..a257e5960
--- /dev/null
+++ b/devtools/server/tests/unit/test_frameclient-02.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Ask for exactly the number of frames we expect.
+ gThreadClient.addOneTimeListener("framesadded", function () {
+ do_check_false(gThreadClient.moreFrames);
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ do_check_true(gThreadClient.fillFrames(3));
+ });
+
+ gDebuggee.eval("(" + function () {
+ var recurseLeft = 1;
+ function recurse() {
+ if (--recurseLeft == 0) {
+ debugger;
+ return;
+ }
+ recurse();
+ }
+ recurse();
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_functiongrips-01.js b/devtools/server/tests/unit/test_functiongrips-01.js
new file mode 100644
index 000000000..c41a7cad5
--- /dev/null
+++ b/devtools/server/tests/unit/test_functiongrips-01.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+ gDebuggee.eval(function stopMe(arg1) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_named_function();
+ });
+ });
+ do_test_pending();
+}
+
+function test_named_function()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame.arguments;
+
+ do_check_eq(args[0].class, "Function");
+ do_check_eq(args[0].name, "stopMe");
+ do_check_eq(args[0].displayName, "stopMe");
+
+ let objClient = gThreadClient.pauseGrip(args[0]);
+ objClient.getParameterNames(function (aResponse) {
+ do_check_eq(aResponse.parameterNames.length, 1);
+ do_check_eq(aResponse.parameterNames[0], "arg1");
+
+ gThreadClient.resume(test_inferred_name_function);
+ });
+
+ });
+
+ gDebuggee.eval("stopMe(stopMe)");
+}
+
+function test_inferred_name_function() {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame.arguments;
+
+ do_check_eq(args[0].class, "Function");
+ // No name for an anonymous function, but it should have an inferred name.
+ do_check_eq(args[0].name, undefined);
+ do_check_eq(args[0].displayName, "o.m");
+
+ let objClient = gThreadClient.pauseGrip(args[0]);
+ objClient.getParameterNames(function (aResponse) {
+ do_check_eq(aResponse.parameterNames.length, 3);
+ do_check_eq(aResponse.parameterNames[0], "foo");
+ do_check_eq(aResponse.parameterNames[1], "bar");
+ do_check_eq(aResponse.parameterNames[2], "baz");
+
+ gThreadClient.resume(test_anonymous_function);
+ });
+ });
+
+ gDebuggee.eval("var o = { m: function(foo, bar, baz) { } }; stopMe(o.m)");
+}
+
+function test_anonymous_function() {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame.arguments;
+
+ do_check_eq(args[0].class, "Function");
+ // No name for an anonymous function, and no inferred name, either.
+ do_check_eq(args[0].name, undefined);
+ do_check_eq(args[0].displayName, undefined);
+
+ let objClient = gThreadClient.pauseGrip(args[0]);
+ objClient.getParameterNames(function (aResponse) {
+ do_check_eq(aResponse.parameterNames.length, 3);
+ do_check_eq(aResponse.parameterNames[0], "foo");
+ do_check_eq(aResponse.parameterNames[1], "bar");
+ do_check_eq(aResponse.parameterNames[2], "baz");
+
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+
+ gDebuggee.eval("stopMe(function(foo, bar, baz) { })");
+}
+
diff --git a/devtools/server/tests/unit/test_get-executable-lines-source-map.js b/devtools/server/tests/unit/test_get-executable-lines-source-map.js
new file mode 100644
index 000000000..bca8eebee
--- /dev/null
+++ b/devtools/server/tests/unit/test_get-executable-lines-source-map.js
@@ -0,0 +1,56 @@
+/* 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/. */
+
+/**
+ * Test if getExecutableLines return correct information
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+const SOURCE_MAPPED_FILE = getFileUrl("sourcemapped.js");
+
+function run_test() {
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-get-executable-lines");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function _onConnect() {
+ attachTestTabAndResume(
+ gClient,
+ "test-get-executable-lines",
+ function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_executable_lines();
+ }
+ );
+ });
+
+ do_test_pending();
+}
+
+function test_executable_lines() {
+ gThreadClient.addOneTimeListener("newSource", function _onNewSource(evt, packet) {
+ do_check_eq(evt, "newSource");
+
+ gThreadClient.getSources(function ({error, sources}) {
+ do_check_true(!error);
+ let source = gThreadClient.source(sources[0]);
+ source.getExecutableLines(function (lines) {
+ do_check_true(arrays_equal([1, 2, 4, 6], lines));
+ finishClient(gClient);
+ });
+ });
+ });
+
+ let code = readFile("sourcemapped.js") + "\n//# sourceMappingURL=" +
+ getFileUrl("source-map-data/sourcemapped.map");
+
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+ SOURCE_MAPPED_FILE, 1);
+}
+
+function arrays_equal(a, b) {
+ return !(a < b || b < a);
+}
diff --git a/devtools/server/tests/unit/test_get-executable-lines.js b/devtools/server/tests/unit/test_get-executable-lines.js
new file mode 100644
index 000000000..233fb6ada
--- /dev/null
+++ b/devtools/server/tests/unit/test_get-executable-lines.js
@@ -0,0 +1,55 @@
+/* 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/. */
+
+/**
+ * Test if getExecutableLines return correct information
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+const SOURCE_MAPPED_FILE = getFileUrl("sourcemapped.js");
+
+function run_test() {
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-get-executable-lines");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function _onConnect() {
+ attachTestTabAndResume(
+ gClient,
+ "test-get-executable-lines",
+ function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_executable_lines();
+ }
+ );
+ });
+
+ do_test_pending();
+}
+
+function test_executable_lines() {
+ gThreadClient.addOneTimeListener("newSource", function _onNewSource(evt, packet) {
+ do_check_eq(evt, "newSource");
+
+ gThreadClient.getSources(function ({error, sources}) {
+ do_check_true(!error);
+ let source = gThreadClient.source(sources[0]);
+ source.getExecutableLines(function (lines) {
+ do_check_true(arrays_equal([2, 5, 7, 8, 10, 12, 14, 16], lines));
+ finishClient(gClient);
+ });
+ });
+ });
+
+ let code = readFile("sourcemapped.js");
+
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+ SOURCE_MAPPED_FILE, 1);
+}
+
+function arrays_equal(a, b) {
+ return !(a < b || b < a);
+}
diff --git a/devtools/server/tests/unit/test_getRuleText.js b/devtools/server/tests/unit/test_getRuleText.js
new file mode 100644
index 000000000..fe735928d
--- /dev/null
+++ b/devtools/server/tests/unit/test_getRuleText.js
@@ -0,0 +1,137 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {getRuleText} = require("devtools/server/actors/styles");
+
+const TEST_DATA = [
+ {
+ desc: "Empty input",
+ input: "",
+ line: 1,
+ column: 1,
+ throws: true
+ },
+ {
+ desc: "Simplest test case",
+ input: "#id{color:red;background:yellow;}",
+ line: 1,
+ column: 1,
+ expected: {offset: 4, text: "color:red;background:yellow;"}
+ },
+ {
+ desc: "Multiple rules test case",
+ input: "#id{color:red;background:yellow;}.class-one .class-two { position:absolute; line-height: 45px}",
+ line: 1,
+ column: 34,
+ expected: {offset: 56, text: " position:absolute; line-height: 45px"}
+ },
+ {
+ desc: "Unclosed rule",
+ input: "#id{color:red;background:yellow;",
+ line: 1,
+ column: 1,
+ expected: {offset: 4, text: "color:red;background:yellow;"}
+ },
+ {
+ desc: "Null input",
+ input: null,
+ line: 1,
+ column: 1,
+ throws: true
+ },
+ {
+ desc: "Missing loc",
+ input: "#id{color:red;background:yellow;}",
+ throws: true
+ },
+ {
+ desc: "Multi-lines CSS",
+ input: [
+ "/* this is a multi line css */",
+ "body {",
+ " color: green;",
+ " background-repeat: no-repeat",
+ "}",
+ " /*something else here */",
+ "* {",
+ " color: purple;",
+ "}"
+ ].join("\n"),
+ line: 7,
+ column: 1,
+ expected: {offset: 116, text: "\n color: purple;\n"}
+ },
+ {
+ desc: "Multi-lines CSS and multi-line rule",
+ input: [
+ "/* ",
+ "* some comments",
+ "*/",
+ "",
+ "body {",
+ " margin: 0;",
+ " padding: 15px 15px 2px 15px;",
+ " color: red;",
+ "}",
+ "",
+ "#header .btn, #header .txt {",
+ " font-size: 100%;",
+ "}",
+ "",
+ "#header #information {",
+ " color: #dddddd;",
+ " font-size: small;",
+ "}",
+ ].join("\n"),
+ line: 5,
+ column: 1,
+ expected: {
+ offset: 30,
+ text: "\n margin: 0;\n padding: 15px 15px 2px 15px;\n color: red;\n"}
+ },
+ {
+ desc: "Content string containing a } character",
+ input: " #id{border:1px solid red;content: '}';color:red;}",
+ line: 1,
+ column: 4,
+ expected: {offset: 7, text: "border:1px solid red;content: '}';color:red;"}
+ },
+ {
+ desc: "Rule contains no tokens",
+ input: "div{}",
+ line: 1,
+ column: 1,
+ expected: {offset: 4, text: ""}
+ },
+];
+
+function run_test() {
+ for (let test of TEST_DATA) {
+ do_print("Starting test: " + test.desc);
+ do_print("Input string " + test.input);
+ let output;
+ try {
+ output = getRuleText(test.input, test.line, test.column);
+ if (test.throws) {
+ do_print("Test should have thrown");
+ do_check_true(false);
+ }
+ } catch (e) {
+ do_print("getRuleText threw an exception with the given input string");
+ if (test.throws) {
+ do_print("Exception expected");
+ do_check_true(true);
+ } else {
+ do_print("Exception unexpected\n" + e);
+ do_check_true(false);
+ }
+ }
+ if (output) {
+ deepEqual(output, test.expected);
+ }
+ }
+}
diff --git a/devtools/server/tests/unit/test_getTextAtLineColumn.js b/devtools/server/tests/unit/test_getTextAtLineColumn.js
new file mode 100644
index 000000000..16ec47608
--- /dev/null
+++ b/devtools/server/tests/unit/test_getTextAtLineColumn.js
@@ -0,0 +1,35 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {getTextAtLineColumn} = require("devtools/server/actors/styles");
+
+const TEST_DATA = [
+ {
+ desc: "simplest",
+ input: "#id{color:red;background:yellow;}",
+ line: 1,
+ column: 5,
+ expected: {offset: 4, text: "color:red;background:yellow;}"}
+ },
+ {
+ desc: "multiple lines",
+ input: "one\n two\n three",
+ line: 3,
+ column: 3,
+ expected: {offset: 11, text: "three"}
+ },
+];
+
+function run_test() {
+ for (let test of TEST_DATA) {
+ do_print("Starting test: " + test.desc);
+ do_print("Input string " + test.input);
+
+ let output = getTextAtLineColumn(test.input, test.line, test.column);
+ deepEqual(output, test.expected);
+ }
+}
diff --git a/devtools/server/tests/unit/test_getyoungestframe.js b/devtools/server/tests/unit/test_getyoungestframe.js
new file mode 100644
index 000000000..035ab5b0c
--- /dev/null
+++ b/devtools/server/tests/unit/test_getyoungestframe.js
@@ -0,0 +1,30 @@
+function run_test()
+{
+ Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+ addDebuggerToGlobal(this);
+ var xpcInspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
+ var g = testGlobal("test1");
+
+ var dbg = new Debugger();
+ dbg.uncaughtExceptionHook = testExceptionHook;
+
+ dbg.addDebuggee(g);
+ dbg.onDebuggerStatement = function (aFrame) {
+ do_check_true(aFrame === dbg.getNewestFrame());
+ // Execute from the nested event loop, dbg.getNewestFrame() won't
+ // be working anymore.
+
+ do_execute_soon(function () {
+ try {
+ do_check_true(aFrame === dbg.getNewestFrame());
+ } finally {
+ xpcInspector.exitNestedEventLoop("test");
+ }
+ });
+ xpcInspector.enterNestedEventLoop("test");
+ };
+
+ g.eval("function debuggerStatement() { debugger; }; debuggerStatement();");
+
+ dbg.enabled = false;
+}
diff --git a/devtools/server/tests/unit/test_ignore_caught_exceptions.js b/devtools/server/tests/unit/test_ignore_caught_exceptions.js
new file mode 100644
index 000000000..a4b221823
--- /dev/null
+++ b/devtools/server/tests/unit/test_ignore_caught_exceptions.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that setting ignoreCaughtExceptions will cause the debugger to ignore
+ * caught exceptions, but not uncaught ones.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aPacket.why.type, "exception");
+ do_check_eq(aPacket.why.exception, "bar");
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ gThreadClient.pauseOnExceptions(true, true);
+ gThreadClient.resume();
+ });
+
+ try {
+ gDebuggee.eval("(" + function () {
+ debugger;
+ try {
+ throw "foo";
+ } catch (e) {}
+ throw "bar";
+ } + ")()");
+ } catch (e) {}
+}
diff --git a/devtools/server/tests/unit/test_ignore_no_interface_exceptions.js b/devtools/server/tests/unit/test_ignore_no_interface_exceptions.js
new file mode 100644
index 000000000..5aaa31de3
--- /dev/null
+++ b/devtools/server/tests/unit/test_ignore_no_interface_exceptions.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that the debugger automatically ignores NS_ERROR_NO_INTERFACE
+ * exceptions, but not normal ones.
+ */
+
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-no-interface");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-no-interface", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.pauseOnExceptions(true, false, function () {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aPacket.why.type, "exception");
+ do_check_eq(aPacket.why.exception, 42);
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function QueryInterface() {
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ }
+ function stopMe() {
+ throw 42;
+ }
+ try {
+ QueryInterface();
+ } catch (e) {}
+ try {
+ stopMe();
+ } catch (e) {}
+ } + ")()");
+ });
+}
diff --git a/devtools/server/tests/unit/test_interrupt.js b/devtools/server/tests/unit/test_interrupt.js
new file mode 100644
index 000000000..34835cc0a
--- /dev/null
+++ b/devtools/server/tests/unit/test_interrupt.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gClient;
+var gDebuggee;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = testGlobal("test-1");
+ DebuggerServer.addTestGlobal(gDebuggee);
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(function (aType, aTraits) {
+ attachTestTab(gClient, "test-1", test_attach);
+ });
+ do_test_pending();
+}
+
+function test_attach(aResponse, aTabClient)
+{
+ aTabClient.attachThread({}, function (aResponse, aThreadClient) {
+ do_check_eq(aThreadClient.paused, true);
+ aThreadClient.resume(function () {
+ test_interrupt(aThreadClient);
+ });
+ });
+}
+
+function test_interrupt(aThreadClient)
+{
+ do_check_eq(aThreadClient.paused, false);
+ aThreadClient.interrupt(function (aResponse) {
+ do_check_eq(aThreadClient.paused, true);
+ aThreadClient.resume(function () {
+ do_check_eq(aThreadClient.paused, false);
+ cleanup();
+ });
+ });
+}
+
+function cleanup()
+{
+ gClient.addListener("closed", function (aEvent) {
+ do_test_finished();
+ });
+ gClient.close();
+}
+
diff --git a/devtools/server/tests/unit/test_layout-reflows-observer.js b/devtools/server/tests/unit/test_layout-reflows-observer.js
new file mode 100644
index 000000000..ff6c07b26
--- /dev/null
+++ b/devtools/server/tests/unit/test_layout-reflows-observer.js
@@ -0,0 +1,286 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the LayoutChangesObserver
+
+var {
+ getLayoutChangesObserver,
+ releaseLayoutChangesObserver,
+ LayoutChangesObserver
+} = require("devtools/server/actors/reflow");
+
+// Override set/clearTimeout on LayoutChangesObserver to avoid depending on
+// time in this unit test. This means that LayoutChangesObserver.eventLoopTimer
+// will be the timeout callback instead of the timeout itself, so test cases
+// will need to execute it to fake a timeout
+LayoutChangesObserver.prototype._setTimeout = cb => cb;
+LayoutChangesObserver.prototype._clearTimeout = function () {};
+
+// Mock the tabActor since we only really want to test the LayoutChangesObserver
+// and don't want to depend on a window object, nor want to test protocol.js
+function MockTabActor() {
+ this.window = new MockWindow();
+ this.windows = [this.window];
+ this.attached = true;
+}
+
+function MockWindow() {}
+MockWindow.prototype = {
+ QueryInterface: function () {
+ let self = this;
+ return {
+ getInterface: function () {
+ return {
+ QueryInterface: function () {
+ if (!self.docShell) {
+ self.docShell = new MockDocShell();
+ }
+ return self.docShell;
+ }
+ };
+ }
+ };
+ },
+ setTimeout: function (cb) {
+ // Simply return the cb itself so that we can execute it in the test instead
+ // of depending on a real timeout
+ return cb;
+ },
+ clearTimeout: function () {}
+};
+
+function MockDocShell() {
+ this.observer = null;
+}
+MockDocShell.prototype = {
+ addWeakReflowObserver: function (observer) {
+ this.observer = observer;
+ },
+ removeWeakReflowObserver: function () {},
+ get chromeEventHandler() {
+ return {
+ addEventListener: (type, cb) => {
+ if (type === "resize") {
+ this.resizeCb = cb;
+ }
+ },
+ removeEventListener: (type, cb) => {
+ if (type === "resize" && cb === this.resizeCb) {
+ this.resizeCb = null;
+ }
+ }
+ };
+ },
+ mockResize: function () {
+ if (this.resizeCb) {
+ this.resizeCb();
+ }
+ }
+};
+
+function run_test() {
+ instancesOfObserversAreSharedBetweenWindows();
+ eventsAreBatched();
+ noEventsAreSentWhenThereAreNoReflowsAndLoopTimeouts();
+ observerIsAlreadyStarted();
+ destroyStopsObserving();
+ stoppingAndStartingSeveralTimesWorksCorrectly();
+ reflowsArentStackedWhenStopped();
+ stackedReflowsAreResetOnStop();
+}
+
+function instancesOfObserversAreSharedBetweenWindows() {
+ do_print("Checking that when requesting twice an instances of the observer " +
+ "for the same TabActor, the instance is shared");
+
+ do_print("Checking 2 instances of the observer for the tabActor 1");
+ let tabActor1 = new MockTabActor();
+ let obs11 = getLayoutChangesObserver(tabActor1);
+ let obs12 = getLayoutChangesObserver(tabActor1);
+ do_check_eq(obs11, obs12);
+
+ do_print("Checking 2 instances of the observer for the tabActor 2");
+ let tabActor2 = new MockTabActor();
+ let obs21 = getLayoutChangesObserver(tabActor2);
+ let obs22 = getLayoutChangesObserver(tabActor2);
+ do_check_eq(obs21, obs22);
+
+ do_print("Checking that observers instances for 2 different tabActors are " +
+ "different");
+ do_check_neq(obs11, obs21);
+
+ releaseLayoutChangesObserver(tabActor1);
+ releaseLayoutChangesObserver(tabActor1);
+ releaseLayoutChangesObserver(tabActor2);
+ releaseLayoutChangesObserver(tabActor2);
+}
+
+function eventsAreBatched() {
+ do_print("Checking that reflow events are batched and only sent when the " +
+ "timeout expires");
+
+ // Note that in this test, we mock the TabActor and its window property, so we
+ // also mock the setTimeout/clearTimeout mechanism and just call the callback
+ // manually
+ let tabActor = new MockTabActor();
+ let observer = getLayoutChangesObserver(tabActor);
+
+ let reflowsEvents = [];
+ let onReflows = (event, reflows) => reflowsEvents.push(reflows);
+ observer.on("reflows", onReflows);
+
+ let resizeEvents = [];
+ let onResize = () => resizeEvents.push("resize");
+ observer.on("resize", onResize);
+
+ do_print("Fake one reflow event");
+ tabActor.window.docShell.observer.reflow();
+ do_print("Checking that no batched reflow event has been emitted");
+ do_check_eq(reflowsEvents.length, 0);
+
+ do_print("Fake another reflow event");
+ tabActor.window.docShell.observer.reflow();
+ do_print("Checking that still no batched reflow event has been emitted");
+ do_check_eq(reflowsEvents.length, 0);
+
+ do_print("Fake a few of resize events too");
+ tabActor.window.docShell.mockResize();
+ tabActor.window.docShell.mockResize();
+ tabActor.window.docShell.mockResize();
+ do_print("Checking that still no batched resize event has been emitted");
+ do_check_eq(resizeEvents.length, 0);
+
+ do_print("Faking timeout expiration and checking that events are sent");
+ observer.eventLoopTimer();
+ do_check_eq(reflowsEvents.length, 1);
+ do_check_eq(reflowsEvents[0].length, 2);
+ do_check_eq(resizeEvents.length, 1);
+
+ observer.off("reflows", onReflows);
+ observer.off("resize", onResize);
+ releaseLayoutChangesObserver(tabActor);
+}
+
+function noEventsAreSentWhenThereAreNoReflowsAndLoopTimeouts() {
+ do_print("Checking that if no reflows were detected and the event batching " +
+ "loop expires, then no reflows event is sent");
+
+ let tabActor = new MockTabActor();
+ let observer = getLayoutChangesObserver(tabActor);
+
+ let reflowsEvents = [];
+ let onReflows = (event, reflows) => reflowsEvents.push(reflows);
+ observer.on("reflows", onReflows);
+
+ do_print("Faking timeout expiration and checking for reflows");
+ observer.eventLoopTimer();
+ do_check_eq(reflowsEvents.length, 0);
+
+ observer.off("reflows", onReflows);
+ releaseLayoutChangesObserver(tabActor);
+}
+
+function observerIsAlreadyStarted() {
+ do_print("Checking that the observer is already started when getting it");
+
+ let tabActor = new MockTabActor();
+ let observer = getLayoutChangesObserver(tabActor);
+ do_check_true(observer.isObserving);
+
+ observer.stop();
+ do_check_false(observer.isObserving);
+
+ observer.start();
+ do_check_true(observer.isObserving);
+
+ releaseLayoutChangesObserver(tabActor);
+}
+
+function destroyStopsObserving() {
+ do_print("Checking that the destroying the observer stops it");
+
+ let tabActor = new MockTabActor();
+ let observer = getLayoutChangesObserver(tabActor);
+ do_check_true(observer.isObserving);
+
+ observer.destroy();
+ do_check_false(observer.isObserving);
+
+ releaseLayoutChangesObserver(tabActor);
+}
+
+function stoppingAndStartingSeveralTimesWorksCorrectly() {
+ do_print("Checking that the stopping and starting several times the observer" +
+ " works correctly");
+
+ let tabActor = new MockTabActor();
+ let observer = getLayoutChangesObserver(tabActor);
+
+ do_check_true(observer.isObserving);
+ observer.start();
+ observer.start();
+ observer.start();
+ do_check_true(observer.isObserving);
+
+ observer.stop();
+ do_check_false(observer.isObserving);
+
+ observer.stop();
+ observer.stop();
+ do_check_false(observer.isObserving);
+
+ releaseLayoutChangesObserver(tabActor);
+}
+
+function reflowsArentStackedWhenStopped() {
+ do_print("Checking that when stopped, reflows aren't stacked in the observer");
+
+ let tabActor = new MockTabActor();
+ let observer = getLayoutChangesObserver(tabActor);
+
+ do_print("Stoping the observer");
+ observer.stop();
+
+ do_print("Faking reflows");
+ tabActor.window.docShell.observer.reflow();
+ tabActor.window.docShell.observer.reflow();
+ tabActor.window.docShell.observer.reflow();
+
+ do_print("Checking that reflows aren't recorded");
+ do_check_eq(observer.reflows.length, 0);
+
+ do_print("Starting the observer and faking more reflows");
+ observer.start();
+ tabActor.window.docShell.observer.reflow();
+ tabActor.window.docShell.observer.reflow();
+ tabActor.window.docShell.observer.reflow();
+
+ do_print("Checking that reflows are recorded");
+ do_check_eq(observer.reflows.length, 3);
+
+ releaseLayoutChangesObserver(tabActor);
+}
+
+function stackedReflowsAreResetOnStop() {
+ do_print("Checking that stacked reflows are reset on stop");
+
+ let tabActor = new MockTabActor();
+ let observer = getLayoutChangesObserver(tabActor);
+
+ tabActor.window.docShell.observer.reflow();
+ do_check_eq(observer.reflows.length, 1);
+
+ observer.stop();
+ do_check_eq(observer.reflows.length, 0);
+
+ tabActor.window.docShell.observer.reflow();
+ do_check_eq(observer.reflows.length, 0);
+
+ observer.start();
+ do_check_eq(observer.reflows.length, 0);
+
+ tabActor.window.docShell.observer.reflow();
+ do_check_eq(observer.reflows.length, 1);
+
+ releaseLayoutChangesObserver(tabActor);
+}
diff --git a/devtools/server/tests/unit/test_listsources-01.js b/devtools/server/tests/unit/test_listsources-01.js
new file mode 100644
index 000000000..231e6a1e4
--- /dev/null
+++ b/devtools/server/tests/unit/test_listsources-01.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check basic getSources functionality.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+var gNumTimesSourcesSent = 0;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.request = (function (request) {
+ return function (aRequest, aOnResponse) {
+ if (aRequest.type === "sources") {
+ ++gNumTimesSourcesSent;
+ }
+ return request.call(this, aRequest, aOnResponse);
+ };
+ }(gClient.request));
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_listsources();
+ });
+ });
+ do_test_pending();
+}
+
+function test_simple_listsources()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.getSources(function (aResponse) {
+ do_check_true(aResponse.sources.some(function (s) {
+ return s.url && s.url.match(/test_listsources-01.js/);
+ }));
+
+ do_check_true(gNumTimesSourcesSent <= 1,
+ "Should only send one sources request at most, even though we"
+ + " might have had to send one to determine feature support.");
+
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+
+ Components.utils.evalInSandbox("var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "var a = 1;\n" + // line0 + 2
+ "var b = 2;\n", // line0 + 3
+ gDebuggee);
+}
diff --git a/devtools/server/tests/unit/test_listsources-02.js b/devtools/server/tests/unit/test_listsources-02.js
new file mode 100644
index 000000000..190a5e31b
--- /dev/null
+++ b/devtools/server/tests/unit/test_listsources-02.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check getting sources before there are any.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+var gNumTimesSourcesSent = 0;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.request = (function (request) {
+ return function (aRequest, aOnResponse) {
+ if (aRequest.type === "sources") {
+ ++gNumTimesSourcesSent;
+ }
+ return request.call(this, aRequest, aOnResponse);
+ };
+ }(gClient.request));
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_listing_zero_sources();
+ });
+ });
+ do_test_pending();
+}
+
+function test_listing_zero_sources()
+{
+ gThreadClient.getSources(function (aPacket) {
+ do_check_true(!aPacket.error);
+ do_check_true(!!aPacket.sources);
+ do_check_eq(aPacket.sources.length, 0);
+
+ do_check_true(gNumTimesSourcesSent <= 1,
+ "Should only send one sources request at most, even though we"
+ + " might have had to send one to determine feature support.");
+
+ finishClient(gClient);
+ });
+}
diff --git a/devtools/server/tests/unit/test_listsources-03.js b/devtools/server/tests/unit/test_listsources-03.js
new file mode 100644
index 000000000..72ebb5e1c
--- /dev/null
+++ b/devtools/server/tests/unit/test_listsources-03.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check getSources functionality when there are lots of sources.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-sources");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-sources", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_listsources();
+ });
+ });
+ do_test_pending();
+}
+
+function test_simple_listsources()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.getSources(function (aResponse) {
+ do_check_true(
+ !aResponse.error,
+ "There shouldn't be an error fetching large amounts of sources.");
+
+ do_check_true(aResponse.sources.some(function (s) {
+ return s.url.match(/foo-999.js$/);
+ }));
+
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+
+ for (let i = 0; i < 1000; i++) {
+ Cu.evalInSandbox("function foo###() {return ###;}".replace(/###/g, i),
+ gDebuggee,
+ "1.8",
+ "http://example.com/foo-" + i + ".js",
+ 1);
+ }
+ gDebuggee.eval("debugger;");
+}
diff --git a/devtools/server/tests/unit/test_listsources-04.js b/devtools/server/tests/unit/test_listsources-04.js
new file mode 100644
index 000000000..6da99a6ce
--- /dev/null
+++ b/devtools/server/tests/unit/test_listsources-04.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check getSources functionality with sourcemaps.
+ */
+
+const {SourceNode} = require("source-map");
+
+function run_test() {
+ run_test_with_server(DebuggerServer, function () {
+ // Bug 1304144 - This test does not run in a worker because the
+ // `rpc` method which talks to the main thread does not work.
+ // run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ do_test_finished();
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(server, cb) {
+ Task.spawn(function*() {
+ initTestDebuggerServer(server);
+ const debuggee = addTestGlobal("test-sources", server);
+ const client = new DebuggerClient(server.connectPipe());
+ yield client.connect();
+ const [,,threadClient] = yield attachTestTabAndResume(client, "test-sources");
+
+ yield threadClient.reconfigure({ useSourceMaps: true });
+ addSources(debuggee);
+
+ threadClient.getSources(Task.async(function* (res) {
+ do_check_true(res.sources.length === 3, "3 sources exist");
+
+ yield threadClient.reconfigure({ useSourceMaps: false });
+
+ threadClient.getSources(function(res) {
+ do_check_true(res.sources.length === 1, "1 source exist");
+ client.close().then(cb);
+ });
+ }));
+ });
+}
+
+function addSources(debuggee) {
+ let { code, map } = (new SourceNode(null, null, null, [
+ new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"),
+ new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"),
+ new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"),
+ ])).toStringWithSourceMap({
+ file: "abc.js",
+ sourceRoot: "http://example.com/www/js/"
+ });
+
+ code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString());
+
+ Components.utils.evalInSandbox(code, debuggee, "1.8",
+ "http://example.com/www/js/abc.js", 1);
+}
diff --git a/devtools/server/tests/unit/test_longstringactor.js b/devtools/server/tests/unit/test_longstringactor.js
new file mode 100644
index 000000000..18b928910
--- /dev/null
+++ b/devtools/server/tests/unit/test_longstringactor.js
@@ -0,0 +1,104 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { LongStringActor } = require("devtools/server/actors/object");
+
+function run_test() {
+ test_LSA_disconnect();
+ test_LSA_grip();
+ test_LSA_onSubstring();
+}
+
+const TEST_STRING = "This is a very long string!";
+
+function makeMockLongStringActor()
+{
+ let string = TEST_STRING;
+ let actor = new LongStringActor(string);
+ actor.actorID = "longString1";
+ actor.registeredPool = {
+ longStringActors: {
+ [string]: actor
+ }
+ };
+ return actor;
+}
+
+function test_LSA_disconnect()
+{
+ let actor = makeMockLongStringActor();
+ do_check_eq(actor.registeredPool.longStringActors[TEST_STRING], actor);
+
+ actor.disconnect();
+ do_check_eq(actor.registeredPool.longStringActors[TEST_STRING], void 0);
+}
+
+function test_LSA_substring()
+{
+ let actor = makeMockLongStringActor();
+ do_check_eq(actor._substring(0, 4), TEST_STRING.substring(0, 4));
+ do_check_eq(actor._substring(6, 9), TEST_STRING.substring(6, 9));
+ do_check_eq(actor._substring(0, TEST_STRING.length), TEST_STRING);
+}
+
+function test_LSA_grip()
+{
+ let actor = makeMockLongStringActor();
+
+ let grip = actor.grip();
+ do_check_eq(grip.type, "longString");
+ do_check_eq(grip.initial, TEST_STRING.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH));
+ do_check_eq(grip.length, TEST_STRING.length);
+ do_check_eq(grip.actor, actor.actorID);
+}
+
+function test_LSA_onSubstring()
+{
+ let actor = makeMockLongStringActor();
+ let response;
+
+ // From the start
+ response = actor.onSubstring({
+ start: 0,
+ end: 4
+ });
+ do_check_eq(response.from, actor.actorID);
+ do_check_eq(response.substring, TEST_STRING.substring(0, 4));
+
+ // In the middle
+ response = actor.onSubstring({
+ start: 5,
+ end: 8
+ });
+ do_check_eq(response.from, actor.actorID);
+ do_check_eq(response.substring, TEST_STRING.substring(5, 8));
+
+ // Whole string
+ response = actor.onSubstring({
+ start: 0,
+ end: TEST_STRING.length
+ });
+ do_check_eq(response.from, actor.actorID);
+ do_check_eq(response.substring, TEST_STRING);
+
+ // Negative index
+ response = actor.onSubstring({
+ start: -5,
+ end: TEST_STRING.length
+ });
+ do_check_eq(response.from, actor.actorID);
+ do_check_eq(response.substring,
+ TEST_STRING.substring(-5, TEST_STRING.length));
+
+ // Past the end
+ response = actor.onSubstring({
+ start: TEST_STRING.length - 5,
+ end: 100
+ });
+ do_check_eq(response.from, actor.actorID);
+ do_check_eq(response.substring,
+ TEST_STRING.substring(TEST_STRING.length - 5, 100));
+}
diff --git a/devtools/server/tests/unit/test_longstringgrips-01.js b/devtools/server/tests/unit/test_longstringgrips-01.js
new file mode 100644
index 000000000..b8e6789c7
--- /dev/null
+++ b/devtools/server/tests/unit/test_longstringgrips-01.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+ gDebuggee.eval(function stopMe(arg1) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_longstring_grip();
+ });
+ });
+ do_test_pending();
+}
+
+function test_longstring_grip()
+{
+ let longString = "All I want is to be a monkey of moderate intelligence who"
+ + " wears a suit... that's why I'm transferring to business school! Maybe I"
+ + " love you so much, I love you no matter who you are pretending to be."
+ + " Enough about your promiscuous mother, Hermes! We have bigger problems."
+ + " For example, if you killed your grandfather, you'd cease to exist! What"
+ + " kind of a father would I be if I said no? Yep, I remember. They came in"
+ + " last at the Olympics, then retired to promote alcoholic beverages! And"
+ + " remember, don't do anything that affects anything, unless it turns out"
+ + " you were supposed to, in which case, for the love of God, don't not do"
+ + " it!";
+
+ DebuggerServer.LONG_STRING_LENGTH = 200;
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame.arguments;
+ do_check_eq(args.length, 1);
+ let grip = args[0];
+
+ try {
+ do_check_eq(grip.type, "longString");
+ do_check_eq(grip.length, longString.length);
+ do_check_eq(grip.initial, longString.substr(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH));
+
+ let longStringClient = gThreadClient.pauseLongString(grip);
+ longStringClient.substring(22, 28, function (aResponse) {
+ try {
+ do_check_eq(aResponse.substring, "monkey");
+ } finally {
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ }
+ });
+ } catch (error) {
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ do_throw(error);
+ });
+ }
+ });
+
+ gDebuggee.eval('stopMe("' + longString + '")');
+}
+
diff --git a/devtools/server/tests/unit/test_longstringgrips-02.js b/devtools/server/tests/unit/test_longstringgrips-02.js
new file mode 100644
index 000000000..01f9c1b8f
--- /dev/null
+++ b/devtools/server/tests/unit/test_longstringgrips-02.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+ gDebuggee.eval(function stopMe(arg1) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(
+ gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_longstring_grip();
+ });
+ });
+ do_test_pending();
+}
+
+function test_longstring_grip()
+{
+ DebuggerServer.LONG_STRING_LENGTH = 200;
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ try {
+ let fakeLongStringGrip = {
+ type: "longString",
+ length: 1000000,
+ actor: "123fakeActor123",
+ initial: ""
+ };
+ let longStringClient = gThreadClient.pauseLongString(fakeLongStringGrip);
+ longStringClient.substring(22, 28, function (aResponse) {
+ try {
+ do_check_true(!!aResponse.error,
+ "We should not get a response, but an error.");
+ } finally {
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ }
+ });
+ } catch (error) {
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ do_throw(error);
+ });
+ }
+ });
+
+ gDebuggee.eval("stopMe()");
+}
+
diff --git a/devtools/server/tests/unit/test_monitor_actor.js b/devtools/server/tests/unit/test_monitor_actor.js
new file mode 100644
index 000000000..17c272d80
--- /dev/null
+++ b/devtools/server/tests/unit/test_monitor_actor.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test the monitor actor.
+ */
+
+"use strict";
+
+function run_test()
+{
+ let EventEmitter = require("devtools/shared/event-emitter");
+
+ function MonitorClient(client, form) {
+ this.client = client;
+ this.actor = form.monitorActor;
+ this.events = ["update"];
+
+ EventEmitter.decorate(this);
+ client.registerClient(this);
+ }
+ MonitorClient.prototype.destroy = function () {
+ this.client.unregisterClient(this);
+ };
+ MonitorClient.prototype.start = function (callback) {
+ this.client.request({
+ to: this.actor,
+ type: "start"
+ }, callback);
+ };
+ MonitorClient.prototype.stop = function (callback) {
+ this.client.request({
+ to: this.actor,
+ type: "stop"
+ }, callback);
+ };
+
+ let monitor, client;
+
+ // Start the monitor actor.
+ get_chrome_actors((c, form) => {
+ client = c;
+ monitor = new MonitorClient(client, form);
+ monitor.on("update", gotUpdate);
+ monitor.start(update);
+ });
+
+ let time = Date.now();
+
+ function update() {
+ let event = {
+ graph: "Test",
+ curve: "test",
+ value: 42,
+ time: time,
+ };
+ Services.obs.notifyObservers(null, "devtools-monitor-update", JSON.stringify(event));
+ }
+
+ function gotUpdate(type, packet) {
+ packet.data.forEach(function (event) {
+ // Ignore updates that were not sent by this test.
+ if (event.graph === "Test") {
+ do_check_eq(event.curve, "test");
+ do_check_eq(event.value, 42);
+ do_check_eq(event.time, time);
+ monitor.stop(function (aResponse) {
+ monitor.destroy();
+ finishClient(client);
+ });
+ }
+ });
+ }
+
+ do_test_pending();
+}
diff --git a/devtools/server/tests/unit/test_nativewrappers.js b/devtools/server/tests/unit/test_nativewrappers.js
new file mode 100644
index 000000000..fbadfcdec
--- /dev/null
+++ b/devtools/server/tests/unit/test_nativewrappers.js
@@ -0,0 +1,30 @@
+function run_test()
+{
+ Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+ addDebuggerToGlobal(this);
+ var g = testGlobal("test1");
+
+ var dbg = new Debugger();
+ dbg.addDebuggee(g);
+ dbg.onDebuggerStatement = function (aFrame) {
+ let args = aFrame.arguments;
+ try {
+ args[0];
+ do_check_true(true);
+ } catch (ex) {
+ do_check_true(false);
+ }
+ };
+
+ g.eval("function stopMe(arg) {debugger;}");
+
+ g2 = testGlobal("test2");
+ g2.g = g;
+ g2.eval("(" + function createBadEvent() {
+ let parser = Components.classes["@mozilla.org/xmlextras/domparser;1"].createInstance(Components.interfaces.nsIDOMParser);
+ let doc = parser.parseFromString("<foo></foo>", "text/xml");
+ g.stopMe(doc.createEvent("MouseEvent"));
+ } + ")()");
+
+ dbg.enabled = false;
+}
diff --git a/devtools/server/tests/unit/test_nesting-01.js b/devtools/server/tests/unit/test_nesting-01.js
new file mode 100644
index 000000000..e515f051e
--- /dev/null
+++ b/devtools/server/tests/unit/test_nesting-01.js
@@ -0,0 +1,48 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can nest event loops when needed in
+// ThreadActor.prototype.unsafeSynchronize.
+
+var gClient;
+var gThreadActor;
+
+function run_test() {
+ initTestDebuggerServer();
+ let gDebuggee = addTestGlobal("test-nesting");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-nesting", function (aResponse, aTabClient, aThreadClient) {
+ // Reach over the protocol connection and get a reference to the thread actor.
+ gThreadActor = aThreadClient._transport._serverConnection.getActor(aThreadClient._actor);
+
+ test_nesting();
+ });
+ });
+ do_test_pending();
+}
+
+function test_nesting() {
+ const thread = gThreadActor;
+ const { resolve, reject, promise: p } = promise.defer();
+
+ let currentStep = 0;
+
+ executeSoon(function () {
+ // Should be on the first step
+ do_check_eq(++currentStep, 1);
+ // We should have one nested event loop from unsfeSynchronize
+ do_check_eq(thread._nestedEventLoops.size, 1);
+ resolve(true);
+ });
+
+ do_check_eq(thread.unsafeSynchronize(p), true);
+
+ // Should be on the second step
+ do_check_eq(++currentStep, 2);
+ // There shouldn't be any nested event loops anymore
+ do_check_eq(thread._nestedEventLoops.size, 0);
+
+ finishClient(gClient);
+}
diff --git a/devtools/server/tests/unit/test_nesting-02.js b/devtools/server/tests/unit/test_nesting-02.js
new file mode 100644
index 000000000..928331be5
--- /dev/null
+++ b/devtools/server/tests/unit/test_nesting-02.js
@@ -0,0 +1,81 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can nest event loops and then automatically exit nested event
+// loops when requested.
+
+var gClient;
+var gThreadActor;
+
+function run_test() {
+ initTestDebuggerServer();
+ let gDebuggee = addTestGlobal("test-nesting");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-nesting", function (aResponse, aTabClient, aThreadClient) {
+ // Reach over the protocol connection and get a reference to the thread
+ // actor.
+ gThreadActor = aThreadClient._transport._serverConnection.getActor(aThreadClient._actor);
+
+ test_nesting();
+ });
+ });
+ do_test_pending();
+}
+
+function test_nesting() {
+ const thread = gThreadActor;
+ const { resolve, reject, promise: p } = promise.defer();
+
+ // The following things should happen (in order):
+ // 1. In the new event loop (created by unsafeSynchronize)
+ // 2. Resolve the promise (shouldn't exit any event loops)
+ // 3. Exit the event loop (should also then exit unsafeSynchronize's event loop)
+ // 4. Be after the unsafeSynchronize call
+ let currentStep = 0;
+
+ executeSoon(function () {
+ let eventLoop;
+
+ executeSoon(function () {
+ // Should be at step 2
+ do_check_eq(++currentStep, 2);
+ // Before resolving, should have the unsafeSynchronize event loop and the
+ // one just created.
+ do_check_eq(thread._nestedEventLoops.size, 2);
+
+ executeSoon(function () {
+ // Should be at step 3
+ do_check_eq(++currentStep, 3);
+ // Before exiting the manually created event loop, should have the
+ // unsafeSynchronize event loop and the manual event loop.
+ do_check_eq(thread._nestedEventLoops.size, 2);
+ // Should have the event loop
+ do_check_true(!!eventLoop);
+ eventLoop.resolve();
+ });
+
+ resolve(true);
+ // Shouldn't exit any event loops because a new one started since the call
+ // to unsafeSynchronize
+ do_check_eq(thread._nestedEventLoops.size, 2);
+ });
+
+ // Should be at step 1
+ do_check_eq(++currentStep, 1);
+ // Should have only the unsafeSynchronize event loop
+ do_check_eq(thread._nestedEventLoops.size, 1);
+ eventLoop = thread._nestedEventLoops.push();
+ eventLoop.enter();
+ });
+
+ do_check_eq(thread.unsafeSynchronize(p), true);
+
+ // Should be on the fourth step
+ do_check_eq(++currentStep, 4);
+ // There shouldn't be any nested event loops anymore
+ do_check_eq(thread._nestedEventLoops.size, 0);
+
+ finishClient(gClient);
+}
diff --git a/devtools/server/tests/unit/test_nesting-03.js b/devtools/server/tests/unit/test_nesting-03.js
new file mode 100644
index 000000000..6a0e5a66b
--- /dev/null
+++ b/devtools/server/tests/unit/test_nesting-03.js
@@ -0,0 +1,51 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can detect nested event loops in tabs with the same URL.
+
+var gClient1, gClient2, gThreadClient1, gThreadClient2;
+
+function run_test() {
+ initTestDebuggerServer();
+ addTestGlobal("test-nesting1");
+ addTestGlobal("test-nesting1");
+ // Conect the first client to the first debuggee.
+ gClient1 = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient1.connect(function () {
+ attachTestThread(gClient1, "test-nesting1", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient1 = aThreadClient;
+ start_second_connection();
+ });
+ });
+ do_test_pending();
+}
+
+function start_second_connection() {
+ gClient2 = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient2.connect(function () {
+ attachTestThread(gClient2, "test-nesting1", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient2 = aThreadClient;
+ test_nesting();
+ });
+ });
+}
+
+function test_nesting() {
+ const { resolve, reject, promise: p } = promise.defer();
+
+ gThreadClient1.resume(aResponse => {
+ do_check_eq(aResponse.error, "wrongOrder");
+ gThreadClient2.resume(aResponse => {
+ do_check_true(!aResponse.error);
+ do_check_eq(aResponse.from, gThreadClient2.actor);
+
+ gThreadClient1.resume(aResponse => {
+ do_check_true(!aResponse.error);
+ do_check_eq(aResponse.from, gThreadClient1.actor);
+
+ gClient1.close(() => finishClient(gClient2));
+ });
+ });
+ });
+}
diff --git a/devtools/server/tests/unit/test_new_source-01.js b/devtools/server/tests/unit/test_new_source-01.js
new file mode 100644
index 000000000..aa2498371
--- /dev/null
+++ b/devtools/server/tests/unit/test_new_source-01.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check basic newSource packet sent from server.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_new_source();
+ });
+ });
+ do_test_pending();
+}
+
+function test_simple_new_source()
+{
+ gThreadClient.addOneTimeListener("newSource", function (aEvent, aPacket) {
+ do_check_eq(aEvent, "newSource");
+ do_check_eq(aPacket.type, "newSource");
+ do_check_true(!!aPacket.source);
+ do_check_true(!!aPacket.source.url.match(/test_new_source-01.js$/));
+
+ finishClient(gClient);
+ });
+
+ Components.utils.evalInSandbox(function inc(n) {
+ return n + 1;
+ }.toString(), gDebuggee);
+}
diff --git a/devtools/server/tests/unit/test_nodelistactor.js b/devtools/server/tests/unit/test_nodelistactor.js
new file mode 100644
index 000000000..4d9ec1a7a
--- /dev/null
+++ b/devtools/server/tests/unit/test_nodelistactor.js
@@ -0,0 +1,26 @@
+/* vim: set 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 a NodeListActor initialized with null nodelist doesn't cause
+// exceptions when calling NodeListActor.form.
+
+const { NodeListActor } = require("devtools/server/actors/inspector");
+
+function run_test() {
+ check_actor_for_list(null);
+ check_actor_for_list([]);
+ check_actor_for_list(["fakenode"]);
+}
+
+function check_actor_for_list(nodelist) {
+ do_print("Checking NodeListActor with nodelist '" + nodelist + "' works.");
+ let actor = new NodeListActor({}, nodelist);
+ let form = actor.form();
+
+ // No exception occured as a exceptions abort the test.
+ ok(true, "No exceptions occured.");
+ equal(form.length, nodelist ? nodelist.length : 0,
+ "NodeListActor reported correct length.");
+}
diff --git a/devtools/server/tests/unit/test_nsjsinspector.js b/devtools/server/tests/unit/test_nsjsinspector.js
new file mode 100644
index 000000000..14a99a308
--- /dev/null
+++ b/devtools/server/tests/unit/test_nsjsinspector.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the basic functionality of the nsIJSInspector component.
+var gCount = 0;
+const MAX = 10;
+var inspector = Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
+var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+
+// Emulate 10 simultaneously-debugged windows from 3 separate client connections.
+var requestor = (count) => ({
+ url:"http://foo/bar/" + count,
+ connection: "conn" + (count % 3)
+});
+
+function run_test()
+{
+ test_nesting();
+}
+
+function test_nesting()
+{
+ do_check_eq(inspector.eventLoopNestLevel, 0);
+
+ tm.currentThread.dispatch({ run: enterEventLoop}, 0);
+
+ do_check_eq(inspector.enterNestedEventLoop(requestor(gCount)), 0);
+ do_check_eq(inspector.eventLoopNestLevel, 0);
+ do_check_eq(inspector.lastNestRequestor, null);
+}
+
+function enterEventLoop() {
+ if (gCount++ < MAX) {
+ tm.currentThread.dispatch({ run: enterEventLoop}, 0);
+
+ let r = Object.create(requestor(gCount));
+
+ do_check_eq(inspector.eventLoopNestLevel, gCount);
+ do_check_eq(inspector.lastNestRequestor.url, requestor(gCount - 1).url);
+ do_check_eq(inspector.lastNestRequestor.connection, requestor(gCount - 1).connection);
+ do_check_eq(inspector.enterNestedEventLoop(requestor(gCount)), gCount);
+ } else {
+ do_check_eq(gCount, MAX + 1);
+ tm.currentThread.dispatch({ run: exitEventLoop}, 0);
+ }
+}
+
+function exitEventLoop() {
+ if (inspector.lastNestRequestor != null) {
+ do_check_eq(inspector.lastNestRequestor.url, requestor(gCount - 1).url);
+ do_check_eq(inspector.lastNestRequestor.connection, requestor(gCount - 1).connection);
+ if (gCount-- > 1) {
+ tm.currentThread.dispatch({ run: exitEventLoop}, 0);
+ }
+
+ do_check_eq(inspector.exitNestedEventLoop(), gCount);
+ do_check_eq(inspector.eventLoopNestLevel, gCount);
+ }
+}
diff --git a/devtools/server/tests/unit/test_objectgrips-01.js b/devtools/server/tests/unit/test_objectgrips-01.js
new file mode 100644
index 000000000..e1857e5b8
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-01.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-grips", aServer);
+ gDebuggee.eval(function stopMe(arg1) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_object_grip();
+ });
+ });
+}
+
+function test_object_grip()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame.arguments;
+
+ do_check_eq(args[0].class, "Object");
+
+ let objClient = gThreadClient.pauseGrip(args[0]);
+ objClient.getOwnPropertyNames(function (aResponse) {
+ do_check_eq(aResponse.ownPropertyNames.length, 3);
+ do_check_eq(aResponse.ownPropertyNames[0], "a");
+ do_check_eq(aResponse.ownPropertyNames[1], "b");
+ do_check_eq(aResponse.ownPropertyNames[2], "c");
+
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+
+ });
+
+ gDebuggee.eval("stopMe({ a: 1, b: true, c: 'foo' })");
+}
+
diff --git a/devtools/server/tests/unit/test_objectgrips-02.js b/devtools/server/tests/unit/test_objectgrips-02.js
new file mode 100644
index 000000000..649d52c64
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-02.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-grips", aServer);
+ gDebuggee.eval(function stopMe(arg1) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_object_grip();
+ });
+ });
+}
+
+function test_object_grip()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame.arguments;
+
+ do_check_eq(args[0].class, "Object");
+
+ let objClient = gThreadClient.pauseGrip(args[0]);
+ objClient.getPrototype(function (aResponse) {
+ do_check_true(aResponse.prototype != undefined);
+
+ let protoClient = gThreadClient.pauseGrip(aResponse.prototype);
+ protoClient.getOwnPropertyNames(function (aResponse) {
+ do_check_eq(aResponse.ownPropertyNames.length, 2);
+ do_check_eq(aResponse.ownPropertyNames[0], "b");
+ do_check_eq(aResponse.ownPropertyNames[1], "c");
+
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ });
+
+ });
+
+ gDebuggee.eval(function Constr() {
+ this.a = 1;
+ }.toString());
+ gDebuggee.eval("Constr.prototype = { b: true, c: 'foo' }; var o = new Constr(); stopMe(o)");
+}
+
diff --git a/devtools/server/tests/unit/test_objectgrips-03.js b/devtools/server/tests/unit/test_objectgrips-03.js
new file mode 100644
index 000000000..8b19db713
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-03.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-grips", aServer);
+ gDebuggee.eval(function stopMe(arg1) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_object_grip();
+ });
+ });
+}
+
+function test_object_grip()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame.arguments;
+
+ do_check_eq(args[0].class, "Object");
+
+ let objClient = gThreadClient.pauseGrip(args[0]);
+ objClient.getProperty("x", function (aResponse) {
+ do_check_eq(aResponse.descriptor.configurable, true);
+ do_check_eq(aResponse.descriptor.enumerable, true);
+ do_check_eq(aResponse.descriptor.writable, true);
+ do_check_eq(aResponse.descriptor.value, 10);
+
+ objClient.getProperty("y", function (aResponse) {
+ do_check_eq(aResponse.descriptor.configurable, true);
+ do_check_eq(aResponse.descriptor.enumerable, true);
+ do_check_eq(aResponse.descriptor.writable, true);
+ do_check_eq(aResponse.descriptor.value, "kaiju");
+
+ objClient.getProperty("a", function (aResponse) {
+ do_check_eq(aResponse.descriptor.configurable, true);
+ do_check_eq(aResponse.descriptor.enumerable, true);
+ do_check_eq(aResponse.descriptor.get.type, "object");
+ do_check_eq(aResponse.descriptor.get.class, "Function");
+ do_check_eq(aResponse.descriptor.set.type, "undefined");
+
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ });
+ });
+
+ });
+
+ gDebuggee.eval("stopMe({ x: 10, y: 'kaiju', get a() { return 42; } })");
+}
+
diff --git a/devtools/server/tests/unit/test_objectgrips-04.js b/devtools/server/tests/unit/test_objectgrips-04.js
new file mode 100644
index 000000000..1662358e0
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-04.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-grips", aServer);
+ gDebuggee.eval(function stopMe(arg1) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_object_grip();
+ });
+ });
+}
+
+function test_object_grip()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame.arguments;
+
+ do_check_eq(args[0].class, "Object");
+
+ let objClient = gThreadClient.pauseGrip(args[0]);
+ objClient.getPrototypeAndProperties(function (aResponse) {
+ do_check_eq(aResponse.ownProperties.x.configurable, true);
+ do_check_eq(aResponse.ownProperties.x.enumerable, true);
+ do_check_eq(aResponse.ownProperties.x.writable, true);
+ do_check_eq(aResponse.ownProperties.x.value, 10);
+
+ do_check_eq(aResponse.ownProperties.y.configurable, true);
+ do_check_eq(aResponse.ownProperties.y.enumerable, true);
+ do_check_eq(aResponse.ownProperties.y.writable, true);
+ do_check_eq(aResponse.ownProperties.y.value, "kaiju");
+
+ do_check_eq(aResponse.ownProperties.a.configurable, true);
+ do_check_eq(aResponse.ownProperties.a.enumerable, true);
+ do_check_eq(aResponse.ownProperties.a.get.type, "object");
+ do_check_eq(aResponse.ownProperties.a.get.class, "Function");
+ do_check_eq(aResponse.ownProperties.a.set.type, "undefined");
+
+ do_check_true(aResponse.prototype != undefined);
+
+ let protoClient = gThreadClient.pauseGrip(aResponse.prototype);
+ protoClient.getOwnPropertyNames(function (aResponse) {
+ do_check_true(aResponse.ownPropertyNames.toString != undefined);
+
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ });
+
+ });
+
+ gDebuggee.eval("stopMe({ x: 10, y: 'kaiju', get a() { return 42; } })");
+}
+
diff --git a/devtools/server/tests/unit/test_objectgrips-05.js b/devtools/server/tests/unit/test_objectgrips-05.js
new file mode 100644
index 000000000..5bbb37d88
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-05.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test checks that frozen objects report themselves as frozen in their
+ * grip.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-grips", aServer);
+ gDebuggee.eval(function stopMe(arg1, arg2) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_object_grip();
+ });
+ });
+}
+
+function test_object_grip()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let obj1 = aPacket.frame.arguments[0];
+ do_check_true(obj1.frozen);
+
+ let obj1Client = gThreadClient.pauseGrip(obj1);
+ do_check_true(obj1Client.isFrozen);
+
+ let obj2 = aPacket.frame.arguments[1];
+ do_check_false(obj2.frozen);
+
+ let obj2Client = gThreadClient.pauseGrip(obj2);
+ do_check_false(obj2Client.isFrozen);
+
+ gThreadClient.resume(_ => {
+ gClient.close().then(gCallback);
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ let obj1 = {};
+ Object.freeze(obj1);
+ stopMe(obj1, {});
+ } + "())");
+}
+
diff --git a/devtools/server/tests/unit/test_objectgrips-06.js b/devtools/server/tests/unit/test_objectgrips-06.js
new file mode 100644
index 000000000..bb9888ab8
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-06.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test checks that sealed objects report themselves as sealed in their
+ * grip.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-grips", aServer);
+ gDebuggee.eval(function stopMe(arg1, arg2) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_object_grip();
+ });
+ });
+}
+
+function test_object_grip()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let obj1 = aPacket.frame.arguments[0];
+ do_check_true(obj1.sealed);
+
+ let obj1Client = gThreadClient.pauseGrip(obj1);
+ do_check_true(obj1Client.isSealed);
+
+ let obj2 = aPacket.frame.arguments[1];
+ do_check_false(obj2.sealed);
+
+ let obj2Client = gThreadClient.pauseGrip(obj2);
+ do_check_false(obj2Client.isSealed);
+
+ gThreadClient.resume(_ => {
+ gClient.close().then(gCallback);
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ let obj1 = {};
+ Object.seal(obj1);
+ stopMe(obj1, {});
+ } + "())");
+}
+
diff --git a/devtools/server/tests/unit/test_objectgrips-07.js b/devtools/server/tests/unit/test_objectgrips-07.js
new file mode 100644
index 000000000..6d9ac11fb
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-07.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This test checks that objects which are not extensible report themselves as
+ * such.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-grips", aServer);
+ gDebuggee.eval(function stopMe(arg1, arg2, arg3, arg4) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_object_grip();
+ });
+ });
+}
+
+function test_object_grip()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let [f, s, ne, e] = aPacket.frame.arguments;
+ let [fClient, sClient, neClient, eClient] = aPacket.frame.arguments.map(
+ a => gThreadClient.pauseGrip(a));
+
+ do_check_false(f.extensible);
+ do_check_false(fClient.isExtensible);
+
+ do_check_false(s.extensible);
+ do_check_false(sClient.isExtensible);
+
+ do_check_false(ne.extensible);
+ do_check_false(neClient.isExtensible);
+
+ do_check_true(e.extensible);
+ do_check_true(eClient.isExtensible);
+
+ gThreadClient.resume(_ => {
+ gClient.close().then(gCallback);
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ let f = {};
+ Object.freeze(f);
+ let s = {};
+ Object.seal(s);
+ let ne = {};
+ Object.preventExtensions(ne);
+ stopMe(f, s, ne, {});
+ } + "())");
+}
+
diff --git a/devtools/server/tests/unit/test_objectgrips-08.js b/devtools/server/tests/unit/test_objectgrips-08.js
new file mode 100644
index 000000000..ecaa7146d
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-08.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-grips", aServer);
+ gDebuggee.eval(function stopMe(arg1) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_object_grip();
+ });
+ });
+}
+
+function test_object_grip()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame.arguments;
+
+ do_check_eq(args[0].class, "Object");
+
+ let objClient = gThreadClient.pauseGrip(args[0]);
+ objClient.getPrototypeAndProperties(function (aResponse) {
+ do_check_eq(aResponse.ownProperties.a.configurable, true);
+ do_check_eq(aResponse.ownProperties.a.enumerable, true);
+ do_check_eq(aResponse.ownProperties.a.writable, true);
+ do_check_eq(aResponse.ownProperties.a.value.type, "Infinity");
+
+ do_check_eq(aResponse.ownProperties.b.configurable, true);
+ do_check_eq(aResponse.ownProperties.b.enumerable, true);
+ do_check_eq(aResponse.ownProperties.b.writable, true);
+ do_check_eq(aResponse.ownProperties.b.value.type, "-Infinity");
+
+ do_check_eq(aResponse.ownProperties.c.configurable, true);
+ do_check_eq(aResponse.ownProperties.c.enumerable, true);
+ do_check_eq(aResponse.ownProperties.c.writable, true);
+ do_check_eq(aResponse.ownProperties.c.value.type, "NaN");
+
+ do_check_eq(aResponse.ownProperties.d.configurable, true);
+ do_check_eq(aResponse.ownProperties.d.enumerable, true);
+ do_check_eq(aResponse.ownProperties.d.writable, true);
+ do_check_eq(aResponse.ownProperties.d.value.type, "-0");
+
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ });
+
+ gDebuggee.eval("stopMe({ a: Infinity, b: -Infinity, c: NaN, d: -0 })");
+}
+
diff --git a/devtools/server/tests/unit/test_objectgrips-09.js b/devtools/server/tests/unit/test_objectgrips-09.js
new file mode 100644
index 000000000..498154b1e
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-09.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/**
+ * This tests exercises getProtypesAndProperties message accepted
+ * by a thread actor.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-grips", aServer);
+ gDebuggee.eval(function stopMe(arg1, arg2) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_object_grip();
+ });
+ });
+}
+
+function test_object_grip()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame.arguments;
+
+ gThreadClient.getPrototypesAndProperties([args[0].actor, args[1].actor], function (aResponse) {
+ let obj1 = aResponse.actors[args[0].actor];
+ let obj2 = aResponse.actors[args[1].actor];
+ do_check_eq(obj1.ownProperties.x.configurable, true);
+ do_check_eq(obj1.ownProperties.x.enumerable, true);
+ do_check_eq(obj1.ownProperties.x.writable, true);
+ do_check_eq(obj1.ownProperties.x.value, 10);
+
+ do_check_eq(obj1.ownProperties.y.configurable, true);
+ do_check_eq(obj1.ownProperties.y.enumerable, true);
+ do_check_eq(obj1.ownProperties.y.writable, true);
+ do_check_eq(obj1.ownProperties.y.value, "kaiju");
+
+ do_check_eq(obj2.ownProperties.z.configurable, true);
+ do_check_eq(obj2.ownProperties.z.enumerable, true);
+ do_check_eq(obj2.ownProperties.z.writable, true);
+ do_check_eq(obj2.ownProperties.z.value, 123);
+
+ do_check_true(obj1.prototype != undefined);
+ do_check_true(obj2.prototype != undefined);
+
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+
+ });
+
+ gDebuggee.eval("stopMe({ x: 10, y: 'kaiju'}, { z: 123 })");
+}
+
diff --git a/devtools/server/tests/unit/test_objectgrips-10.js b/devtools/server/tests/unit/test_objectgrips-10.js
new file mode 100644
index 000000000..a5d1b18c6
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-10.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+// Test that closures can be inspected.
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-closures");
+
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-closures", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_object_grip();
+ });
+ });
+ do_test_pending();
+}
+
+function test_object_grip()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let person = aPacket.frame.environment.bindings.variables.person;
+
+ do_check_eq(person.value.class, "Object");
+
+ let personClient = gThreadClient.pauseGrip(person.value);
+ personClient.getPrototypeAndProperties(aResponse => {
+ do_check_eq(aResponse.ownProperties.getName.value.class, "Function");
+
+ do_check_eq(aResponse.ownProperties.getAge.value.class, "Function");
+
+ do_check_eq(aResponse.ownProperties.getFoo.value.class, "Function");
+
+ let getNameClient = gThreadClient.pauseGrip(aResponse.ownProperties.getName.value);
+ let getAgeClient = gThreadClient.pauseGrip(aResponse.ownProperties.getAge.value);
+ let getFooClient = gThreadClient.pauseGrip(aResponse.ownProperties.getFoo.value);
+ getNameClient.getScope(aResponse => {
+ do_check_eq(aResponse.scope.bindings.arguments[0].name.value, "Bob");
+
+ getAgeClient.getScope(aResponse => {
+ do_check_eq(aResponse.scope.bindings.arguments[1].age.value, 58);
+
+ getFooClient.getScope(aResponse => {
+ do_check_eq(aResponse.scope.bindings.variables.foo.value, 10);
+
+ gThreadClient.resume(() => finishClient(gClient));
+ });
+ });
+ });
+ });
+
+ });
+
+ gDebuggee.eval("(" + function () {
+ var PersonFactory = function (name, age) {
+ var foo = 10;
+ return {
+ getName: function () { return name; },
+ getAge: function () { return age; },
+ getFoo: function () { foo = Date.now(); return foo; }
+ };
+ };
+ var person = new PersonFactory("Bob", 58);
+ debugger;
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_objectgrips-11.js b/devtools/server/tests/unit/test_objectgrips-11.js
new file mode 100644
index 000000000..1ad5c353a
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-11.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we get the magic properties on Error objects.
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+ gDebuggee.eval(function stopMe(arg1) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_object_grip();
+ });
+ });
+ do_test_pending();
+}
+
+function test_object_grip()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame.arguments;
+
+ let objClient = gThreadClient.pauseGrip(args[0]);
+ objClient.getOwnPropertyNames(function (aResponse) {
+ var opn = aResponse.ownPropertyNames;
+ do_check_eq(opn.length, 4);
+ opn.sort();
+ do_check_eq(opn[0], "columnNumber");
+ do_check_eq(opn[1], "fileName");
+ do_check_eq(opn[2], "lineNumber");
+ do_check_eq(opn[3], "message");
+
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+
+ });
+
+ gDebuggee.eval("stopMe(new TypeError('error message text'))");
+}
+
diff --git a/devtools/server/tests/unit/test_objectgrips-12.js b/devtools/server/tests/unit/test_objectgrips-12.js
new file mode 100644
index 000000000..32d4d47e0
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-12.js
@@ -0,0 +1,162 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test getDisplayString.
+
+Cu.import("resource://testing-common/PromiseTestUtils.jsm", this);
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+ gDebuggee.eval(function stopMe(arg1) {
+ debugger;
+ }.toString());
+
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_display_string();
+ });
+ });
+ do_test_pending();
+}
+
+function test_display_string()
+{
+ const testCases = [
+ {
+ input: "new Boolean(true)",
+ output: "true"
+ },
+ {
+ input: "new Number(5)",
+ output: "5"
+ },
+ {
+ input: "new String('foo')",
+ output: "foo"
+ },
+ {
+ input: "new Map()",
+ output: "[object Map]"
+ },
+ {
+ input: "[,,,,,,,]",
+ output: ",,,,,,"
+ },
+ {
+ input: "[1, 2, 3]",
+ output: "1,2,3"
+ },
+ {
+ input: "[undefined, null, true, 'foo', 5]",
+ output: ",,true,foo,5"
+ },
+ {
+ input: "[{},{}]",
+ output: "[object Object],[object Object]"
+ },
+ {
+ input: "(" + function () {
+ const arr = [1];
+ arr.push(arr);
+ return arr;
+ } + ")()",
+ output: "1,"
+ },
+ {
+ input: "{}",
+ output: "[object Object]"
+ },
+ {
+ input: "Object.create(null)",
+ output: "[object Object]"
+ },
+ {
+ input: "new Error('foo')",
+ output: "Error: foo"
+ },
+ {
+ input: "new SyntaxError()",
+ output: "SyntaxError"
+ },
+ {
+ input: "new ReferenceError('')",
+ output: "ReferenceError"
+ },
+ {
+ input: "(" + function () {
+ const err = new Error("bar");
+ err.name = "foo";
+ return err;
+ } + ")()",
+ output: "foo: bar"
+ },
+ {
+ input: "() => {}",
+ output: "() => {}"
+ },
+ {
+ input: "function (foo, bar) {}",
+ output: "function (foo, bar) {}"
+ },
+ {
+ input: "function foo(bar) {}",
+ output: "function foo(bar) {}"
+ },
+ {
+ input: "Array",
+ output: Array + ""
+ },
+ {
+ input: "/foo[bar]/g",
+ output: "/foo[bar]/g"
+ },
+ {
+ input: "new Proxy({}, {})",
+ output: "[object Object]"
+ },
+ {
+ input: "Promise.resolve(5)",
+ output: "Promise (fulfilled: 5)"
+ },
+ {
+ // This rejection is left uncaught, see expectUncaughtRejection below.
+ input: "Promise.reject(new Error())",
+ output: "Promise (rejected: Error)"
+ },
+ {
+ input: "new Promise(function () {})",
+ output: "Promise (pending)"
+ }
+ ];
+
+ PromiseTestUtils.expectUncaughtRejection(/Error/);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ const args = aPacket.frame.arguments;
+
+ (function loop() {
+ const objClient = gThreadClient.pauseGrip(args.pop());
+ objClient.getDisplayString(function ({ displayString }) {
+ do_check_eq(displayString, testCases.pop().output);
+ if (args.length) {
+ loop();
+ } else {
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ }
+ });
+ })();
+ });
+
+ const inputs = testCases.map(({ input }) => input).join(",");
+ gDebuggee.eval("stopMe(" + inputs + ")");
+}
diff --git a/devtools/server/tests/unit/test_objectgrips-13.js b/devtools/server/tests/unit/test_objectgrips-13.js
new file mode 100644
index 000000000..166e8a0d5
--- /dev/null
+++ b/devtools/server/tests/unit/test_objectgrips-13.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that ObjectClient.prototype.getDefinitionSite and the "definitionSite"
+// request work properly.
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+ Components.utils.evalInSandbox(function stopMe() {
+ debugger;
+ }.toString(), gDebuggee);
+
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ add_pause_listener();
+ });
+ });
+ do_test_pending();
+}
+
+function add_pause_listener()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ const [funcGrip, objGrip] = aPacket.frame.arguments;
+ const func = gThreadClient.pauseGrip(funcGrip);
+ const obj = gThreadClient.pauseGrip(objGrip);
+ test_definition_site(func, obj);
+ });
+
+ eval_code();
+}
+
+function eval_code() {
+ Components.utils.evalInSandbox([
+ "this.line0 = Error().lineNumber;",
+ "function f() {}",
+ "stopMe(f, {});"
+ ].join("\n"), gDebuggee);
+}
+
+function test_definition_site(func, obj) {
+ func.getDefinitionSite(({ error, source, line, column }) => {
+ do_check_true(!error);
+ do_check_eq(source.url, getFilePath("test_objectgrips-13.js"));
+ do_check_eq(line, gDebuggee.line0 + 1);
+ do_check_eq(column, 0);
+
+ test_bad_definition_site(obj);
+ });
+}
+
+function test_bad_definition_site(obj) {
+ try {
+ obj._client.request("definitionSite", () => do_check_true(false));
+ } catch (e) {
+ gThreadClient.resume(() => finishClient(gClient));
+ }
+}
diff --git a/devtools/server/tests/unit/test_pause_exceptions-01.js b/devtools/server/tests/unit/test_pause_exceptions-01.js
new file mode 100644
index 000000000..56ee6816d
--- /dev/null
+++ b/devtools/server/tests/unit/test_pause_exceptions-01.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that setting pauseOnExceptions to true will cause the debuggee to pause
+ * when an exceptions is thrown.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aPacket.why.type, "exception");
+ do_check_eq(aPacket.why.exception, 42);
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ gThreadClient.pauseOnExceptions(true);
+ gThreadClient.resume();
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe() {
+ debugger;
+ throw 42;
+ }
+ try {
+ stopMe();
+ } catch (e) {}
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_pause_exceptions-02.js b/devtools/server/tests/unit/test_pause_exceptions-02.js
new file mode 100644
index 000000000..fa9b419f0
--- /dev/null
+++ b/devtools/server/tests/unit/test_pause_exceptions-02.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that setting pauseOnExceptions to true when the debugger isn't in a
+ * paused state will cause the debuggee to pause when an exceptions is thrown.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.pauseOnExceptions(true, false, function () {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aPacket.why.type, "exception");
+ do_check_eq(aPacket.why.exception, 42);
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe() {
+ throw 42;
+ }
+ try {
+ stopMe();
+ } catch (e) {}
+ } + ")()");
+ });
+}
diff --git a/devtools/server/tests/unit/test_pauselifetime-01.js b/devtools/server/tests/unit/test_pauselifetime-01.js
new file mode 100644
index 000000000..71c2ddae7
--- /dev/null
+++ b/devtools/server/tests/unit/test_pauselifetime-01.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that pause-lifetime grips go away correctly after a resume.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let pauseActor = aPacket.actor;
+
+ // Make a bogus request to the pause-liftime actor. Should get
+ // unrecognized-packet-type (and not no-such-actor).
+ gClient.request({ to: pauseActor, type: "bogusRequest" }, function (aResponse) {
+ do_check_eq(aResponse.error, "unrecognizedPacketType");
+
+ gThreadClient.resume(function () {
+ // Now that we've resumed, should get no-such-actor for the
+ // same request.
+ gClient.request({ to: pauseActor, type: "bogusRequest" }, function (aResponse) {
+ do_check_eq(aResponse.error, "noSuchActor");
+ finishClient(gClient);
+ });
+ });
+
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe() {
+ debugger;
+ }
+ stopMe();
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_pauselifetime-02.js b/devtools/server/tests/unit/test_pauselifetime-02.js
new file mode 100644
index 000000000..6c90725bb
--- /dev/null
+++ b/devtools/server/tests/unit/test_pauselifetime-02.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that pause-lifetime grips go away correctly after a resume.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame.arguments;
+ let objActor = args[0].actor;
+ do_check_eq(args[0].class, "Object");
+ do_check_true(!!objActor);
+
+ // Make a bogus request to the grip actor. Should get
+ // unrecognized-packet-type (and not no-such-actor).
+ gClient.request({ to: objActor, type: "bogusRequest" }, function (aResponse) {
+ do_check_eq(aResponse.error, "unrecognizedPacketType");
+
+ gThreadClient.resume(function () {
+ // Now that we've resumed, should get no-such-actor for the
+ // same request.
+ gClient.request({ to: objActor, type: "bogusRequest" }, function (aResponse) {
+ do_check_eq(aResponse.error, "noSuchActor");
+ finishClient(gClient);
+ });
+ });
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(aObject) {
+ debugger;
+ }
+ stopMe({ foo: "bar" });
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_pauselifetime-03.js b/devtools/server/tests/unit/test_pauselifetime-03.js
new file mode 100644
index 000000000..9fca887b7
--- /dev/null
+++ b/devtools/server/tests/unit/test_pauselifetime-03.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that pause-lifetime grip clients are marked invalid after a resume.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame.arguments;
+ let objActor = args[0].actor;
+ do_check_eq(args[0].class, "Object");
+ do_check_true(!!objActor);
+
+ let objClient = gThreadClient.pauseGrip(args[0]);
+ do_check_true(objClient.valid);
+
+ // Make a bogus request to the grip actor. Should get
+ // unrecognized-packet-type (and not no-such-actor).
+ gClient.request({ to: objActor, type: "bogusRequest" }, function (aResponse) {
+ do_check_eq(aResponse.error, "unrecognizedPacketType");
+ do_check_true(objClient.valid);
+
+ gThreadClient.resume(function () {
+ // Now that we've resumed, should get no-such-actor for the
+ // same request.
+ gClient.request({ to: objActor, type: "bogusRequest" }, function (aResponse) {
+ do_check_false(objClient.valid);
+ do_check_eq(aResponse.error, "noSuchActor");
+ finishClient(gClient);
+ });
+ });
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(aObject) {
+ debugger;
+ }
+ stopMe({ foo: "bar" });
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_pauselifetime-04.js b/devtools/server/tests/unit/test_pauselifetime-04.js
new file mode 100644
index 000000000..c863da921
--- /dev/null
+++ b/devtools/server/tests/unit/test_pauselifetime-04.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that requesting a pause actor for the same value multiple
+ * times returns the same actor.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-stack");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_pause_frame();
+ });
+ });
+ do_test_pending();
+}
+
+function test_pause_frame()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let args = aPacket.frame.arguments;
+ let objActor1 = args[0].actor;
+
+ gThreadClient.getFrames(0, 1, function (aResponse) {
+ let frame = aResponse.frames[0];
+ do_check_eq(objActor1, frame.arguments[0].actor);
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(aObject) {
+ debugger;
+ }
+ stopMe({ foo: "bar" });
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_profiler_activation-01.js b/devtools/server/tests/unit/test_profiler_activation-01.js
new file mode 100644
index 000000000..31efbb5e3
--- /dev/null
+++ b/devtools/server/tests/unit/test_profiler_activation-01.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests whether the profiler module and actor have the correct state on
+ * initialization, activation, and when a clients' connection closes.
+ */
+
+const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+const MAX_PROFILER_ENTRIES = 10000000;
+
+function run_test()
+{
+ // Ensure the profiler is not running when the test starts (it could
+ // happen if the MOZ_PROFILER_STARTUP environment variable is set).
+ Profiler.StopProfiler();
+
+ get_chrome_actors((client1, form1) => {
+ let actor1 = form1.profilerActor;
+ get_chrome_actors((client2, form2) => {
+ let actor2 = form2.profilerActor;
+ test_activate(client1, actor1, client2, actor2, () => {
+ do_test_finished();
+ });
+ });
+ });
+
+ do_test_pending();
+}
+
+function test_activate(client1, actor1, client2, actor2, callback) {
+ // Profiler should be inactive at this point.
+ client1.request({ to: actor1, type: "isActive" }, response => {
+ do_check_false(Profiler.IsActive());
+ do_check_false(response.isActive);
+ do_check_eq(response.currentTime, undefined);
+ do_check_true(typeof response.position === "number");
+ do_check_true(typeof response.totalSize === "number");
+ do_check_true(typeof response.generation === "number");
+
+ // Start the profiler on the first connection....
+ client1.request({ to: actor1, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => {
+ do_check_true(Profiler.IsActive());
+ do_check_true(response.started);
+ do_check_true(typeof response.position === "number");
+ do_check_true(typeof response.totalSize === "number");
+ do_check_true(typeof response.generation === "number");
+ do_check_true(response.position >= 0 && response.position < response.totalSize);
+ do_check_true(response.totalSize === MAX_PROFILER_ENTRIES);
+
+ // On the next connection just make sure the actor has been instantiated.
+ client2.request({ to: actor2, type: "isActive" }, response => {
+ do_check_true(Profiler.IsActive());
+ do_check_true(response.isActive);
+ do_check_true(response.currentTime > 0);
+ do_check_true(typeof response.position === "number");
+ do_check_true(typeof response.totalSize === "number");
+ do_check_true(typeof response.generation === "number");
+ do_check_true(response.position >= 0 && response.position < response.totalSize);
+ do_check_true(response.totalSize === MAX_PROFILER_ENTRIES);
+
+ let origConnectionClosed = DebuggerServer._connectionClosed;
+
+ DebuggerServer._connectionClosed = function (conn) {
+ origConnectionClosed.call(this, conn);
+
+ // The first client is the only actor that started the profiler,
+ // however the second client can request the accumulated profile data
+ // at any moment, so the profiler module shouldn't have deactivated.
+ do_check_true(Profiler.IsActive());
+
+ DebuggerServer._connectionClosed = function (conn) {
+ origConnectionClosed.call(this, conn);
+
+ // Now there are no open clients at all, it should *definitely*
+ // be deactivated by now.
+ do_check_false(Profiler.IsActive());
+
+ callback();
+ };
+ client2.close();
+ };
+ client1.close();
+ });
+ });
+ });
+}
diff --git a/devtools/server/tests/unit/test_profiler_activation-02.js b/devtools/server/tests/unit/test_profiler_activation-02.js
new file mode 100644
index 000000000..cf06b1e06
--- /dev/null
+++ b/devtools/server/tests/unit/test_profiler_activation-02.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests whether the profiler actor correctly handles the case where the
+ * built-in module was already started.
+ */
+
+const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+const WAIT_TIME = 1000; // ms
+
+function run_test()
+{
+ // Ensure the profiler is already running when the test starts.
+ Profiler.StartProfiler(1000000, 1, ["js"], 1);
+
+ DevToolsUtils.waitForTime(WAIT_TIME).then(() => {
+
+ get_chrome_actors((client, form) => {
+ let actor = form.profilerActor;
+ test_start_time(client, actor, () => {
+ client.close().then(do_test_finished);
+ });
+ });
+ });
+
+ do_test_pending();
+}
+
+function test_start_time(client, actor, callback) {
+ // Profiler should already be active at this point.
+ client.request({ to: actor, type: "isActive" }, firstResponse => {
+ do_check_true(Profiler.IsActive());
+ do_check_true(firstResponse.isActive);
+ do_check_true(firstResponse.currentTime > 0);
+
+ client.request({ to: actor, type: "getProfile" }, secondResponse => {
+ do_check_true("profile" in secondResponse);
+ do_check_true(secondResponse.currentTime > firstResponse.currentTime);
+
+ callback();
+ });
+ });
+}
diff --git a/devtools/server/tests/unit/test_profiler_bufferstatus.js b/devtools/server/tests/unit/test_profiler_bufferstatus.js
new file mode 100644
index 000000000..9c86bf817
--- /dev/null
+++ b/devtools/server/tests/unit/test_profiler_bufferstatus.js
@@ -0,0 +1,127 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the profiler actor returns its buffer status via getBufferInfo.
+ */
+
+const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+const INITIAL_WAIT_TIME = 100; // ms
+const MAX_WAIT_TIME = 20000; // ms
+const MAX_PROFILER_ENTRIES = 10000000;
+
+function run_test()
+{
+ // Ensure the profiler is not running when the test starts (it could
+ // happen if the MOZ_PROFILER_STARTUP environment variable is set).
+ Profiler.StopProfiler();
+
+ get_chrome_actors((client, form) => {
+ let actor = form.profilerActor;
+ check_empty_buffer(client, actor, () => {
+ activate_profiler(client, actor, startTime => {
+ wait_for_samples(client, actor, () => {
+ check_buffer(client, actor, () => {
+ deactivate_profiler(client, actor, () => {
+ client.close().then(do_test_finished);
+ });
+ });
+ });
+ });
+ });
+ });
+
+ do_test_pending();
+}
+
+function check_buffer(client, actor, callback)
+{
+ client.request({ to: actor, type: "isActive" }, response => {
+ do_check_true(typeof response.position === "number");
+ do_check_true(typeof response.totalSize === "number");
+ do_check_true(typeof response.generation === "number");
+ do_check_true(response.position > 0 && response.position < response.totalSize);
+ do_check_true(response.totalSize === MAX_PROFILER_ENTRIES);
+ // There's no way we'll fill the buffer in this test.
+ do_check_true(response.generation === 0);
+
+ callback();
+ });
+}
+
+function check_empty_buffer(client, actor, callback)
+{
+ client.request({ to: actor, type: "isActive" }, response => {
+ do_check_false(Profiler.IsActive());
+ do_check_false(response.isActive);
+ do_check_true(response.position === void 0);
+ do_check_true(response.totalSize === void 0);
+ do_check_true(response.generation === void 0);
+ do_check_false(response.isActive);
+ do_check_eq(response.currentTime, undefined);
+ calback();
+ });
+}
+
+function activate_profiler(client, actor, callback)
+{
+ client.request({ to: actor, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => {
+ do_check_true(response.started);
+ client.request({ to: actor, type: "isActive" }, response => {
+ do_check_true(response.isActive);
+ callback(response.currentTime);
+ });
+ });
+}
+
+function deactivate_profiler(client, actor, callback)
+{
+ client.request({ to: actor, type: "stopProfiler" }, response => {
+ do_check_false(response.started);
+ client.request({ to: actor, type: "isActive" }, response => {
+ do_check_false(response.isActive);
+ callback();
+ });
+ });
+}
+
+function wait_for_samples(client, actor, callback)
+{
+ function attempt(delay)
+ {
+ // No idea why, but Components.stack.sourceLine returns null.
+ let funcLine = Components.stack.lineNumber - 3;
+
+ // Spin for the requested time, then take a sample.
+ let start = Date.now();
+ let stack;
+ do_print("Attempt: delay = " + delay);
+ while (Date.now() - start < delay) { stack = Components.stack; }
+ do_print("Attempt: finished waiting.");
+
+ client.request({ to: actor, type: "getProfile" }, response => {
+ // At this point, we may or may not have samples, depending on
+ // whether the spin loop above has given the profiler enough time
+ // to get started.
+ if (response.profile.threads[0].samples.length == 0) {
+ if (delay < MAX_WAIT_TIME) {
+ // Double the spin-wait time and try again.
+ do_print("Attempt: no samples, going around again.");
+ return attempt(delay * 2);
+ } else {
+ // We've waited long enough, so just fail.
+ do_print("Attempt: waited a long time, but no samples were collected.");
+ do_print("Giving up.");
+ do_check_true(false);
+ return;
+ }
+ }
+ callback();
+ });
+ }
+
+ // Start off with a 100 millisecond delay.
+ attempt(INITIAL_WAIT_TIME);
+}
diff --git a/devtools/server/tests/unit/test_profiler_close.js b/devtools/server/tests/unit/test_profiler_close.js
new file mode 100644
index 000000000..a8b3040fd
--- /dev/null
+++ b/devtools/server/tests/unit/test_profiler_close.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests whether the profiler module is kept active when there are multiple
+ * client consumers and one requests deactivation.
+ */
+
+const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+
+function run_test()
+{
+ get_chrome_actors((client1, form1) => {
+ let actor1 = form1.profilerActor;
+ get_chrome_actors((client2, form2) => {
+ let actor2 = form2.profilerActor;
+ test_close(client1, actor1, client2, actor2, () => {
+ client1.close(() => {
+ client2.close(() => {
+ do_test_finished();
+ });
+ });
+ });
+ });
+ });
+
+ do_test_pending();
+}
+
+function activate_profiler(client, actor, callback)
+{
+ client.request({ to: actor, type: "startProfiler" }, response => {
+ do_check_true(response.started);
+ do_check_true(Profiler.IsActive());
+
+ client.request({ to: actor, type: "isActive" }, response => {
+ do_check_true(response.isActive);
+ callback();
+ });
+ });
+}
+
+function deactivate_profiler(client, actor, callback)
+{
+ client.request({ to: actor, type: "stopProfiler" }, response => {
+ do_check_false(response.started);
+ do_check_true(Profiler.IsActive());
+
+ client.request({ to: actor, type: "isActive" }, response => {
+ do_check_true(response.isActive);
+ callback();
+ });
+ });
+}
+
+function test_close(client1, actor1, client2, actor2, callback)
+{
+ activate_profiler(client1, actor1, () => {
+ activate_profiler(client2, actor2, () => {
+ deactivate_profiler(client1, actor1, () => {
+ deactivate_profiler(client2, actor2, () => {
+ callback();
+ });
+ });
+ });
+ });
+}
diff --git a/devtools/server/tests/unit/test_profiler_data.js b/devtools/server/tests/unit/test_profiler_data.js
new file mode 100644
index 000000000..2a79eed1f
--- /dev/null
+++ b/devtools/server/tests/unit/test_profiler_data.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the profiler actor can correctly retrieve a profile after
+ * it is activated.
+ */
+
+const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+const INITIAL_WAIT_TIME = 100; // ms
+const MAX_WAIT_TIME = 20000; // ms
+
+function run_test()
+{
+ get_chrome_actors((client, form) => {
+ let actor = form.profilerActor;
+ activate_profiler(client, actor, startTime => {
+ test_data(client, actor, startTime, () => {
+ deactivate_profiler(client, actor, () => {
+ client.close().then(do_test_finished);
+ });
+ });
+ });
+ });
+
+ do_test_pending();
+}
+
+function activate_profiler(client, actor, callback)
+{
+ client.request({ to: actor, type: "startProfiler" }, response => {
+ do_check_true(response.started);
+ client.request({ to: actor, type: "isActive" }, response => {
+ do_check_true(response.isActive);
+ callback(response.currentTime);
+ });
+ });
+}
+
+function deactivate_profiler(client, actor, callback)
+{
+ client.request({ to: actor, type: "stopProfiler" }, response => {
+ do_check_false(response.started);
+ client.request({ to: actor, type: "isActive" }, response => {
+ do_check_false(response.isActive);
+ callback();
+ });
+ });
+}
+
+function test_data(client, actor, startTime, callback)
+{
+ function attempt(delay)
+ {
+ // No idea why, but Components.stack.sourceLine returns null.
+ let funcLine = Components.stack.lineNumber - 3;
+
+ // Spin for the requested time, then take a sample.
+ let start = Date.now();
+ let stack;
+ do_print("Attempt: delay = " + delay);
+ while (Date.now() - start < delay) { stack = Components.stack; }
+ do_print("Attempt: finished waiting.");
+
+ client.request({ to: actor, type: "getProfile", startTime }, response => {
+ // Any valid getProfile response should have the following top
+ // level structure.
+ do_check_eq(typeof response.profile, "object");
+ do_check_eq(typeof response.profile.meta, "object");
+ do_check_eq(typeof response.profile.meta.platform, "string");
+ do_check_eq(typeof response.profile.threads, "object");
+ do_check_eq(typeof response.profile.threads[0], "object");
+ do_check_eq(typeof response.profile.threads[0].samples, "object");
+
+ // At this point, we may or may not have samples, depending on
+ // whether the spin loop above has given the profiler enough time
+ // to get started.
+ if (response.profile.threads[0].samples.length == 0) {
+ if (delay < MAX_WAIT_TIME) {
+ // Double the spin-wait time and try again.
+ do_print("Attempt: no samples, going around again.");
+ return attempt(delay * 2);
+ } else {
+ // We've waited long enough, so just fail.
+ do_print("Attempt: waited a long time, but no samples were collected.");
+ do_print("Giving up.");
+ do_check_true(false);
+ return;
+ }
+ }
+
+ // Now check the samples. At least one sample is expected to
+ // have been in the busy wait above.
+ let loc = stack.name + " (" + stack.filename + ":" + funcLine + ")";
+ let thread0 = response.profile.threads[0];
+ do_check_true(thread0.samples.data.some(sample => {
+ let frames = getInflatedStackLocations(thread0, sample);
+ return frames.length != 0 &&
+ frames.some(location => (location == loc));
+ }));
+
+ callback();
+ });
+ }
+
+ // Start off with a 100 millisecond delay.
+ attempt(INITIAL_WAIT_TIME);
+}
diff --git a/devtools/server/tests/unit/test_profiler_events-01.js b/devtools/server/tests/unit/test_profiler_events-01.js
new file mode 100644
index 000000000..b8ca592b9
--- /dev/null
+++ b/devtools/server/tests/unit/test_profiler_events-01.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests the event notification service for the profiler actor.
+ */
+
+const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+const { ProfilerFront } = require("devtools/shared/fronts/profiler");
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let [client, form] = yield getChromeActors();
+ let front = new ProfilerFront(client, form);
+
+ let events = [0, 0, 0, 0];
+ front.on("console-api-profiler", () => events[0]++);
+ front.on("profiler-started", () => events[1]++);
+ front.on("profiler-stopped", () => events[2]++);
+ client.addListener("eventNotification", (type, response) => {
+ do_check_true(type === "eventNotification");
+ events[3]++;
+ });
+
+ yield front.startProfiler();
+ yield front.stopProfiler();
+
+ // All should be empty without binding events
+ do_check_true(events[0] === 0);
+ do_check_true(events[1] === 0);
+ do_check_true(events[2] === 0);
+ do_check_true(events[3] === 0);
+
+ let ret = yield front.registerEventNotifications({ events: ["console-api-profiler", "profiler-started", "profiler-stopped"] });
+ do_check_true(ret.registered.length === 3);
+
+ yield front.startProfiler();
+ do_check_true(events[0] === 0);
+ do_check_true(events[1] === 1);
+ do_check_true(events[2] === 0);
+ do_check_true(events[3] === 1, "compatibility events supported for eventNotifications");
+
+ yield front.stopProfiler();
+ do_check_true(events[0] === 0);
+ do_check_true(events[1] === 1);
+ do_check_true(events[2] === 1);
+ do_check_true(events[3] === 2, "compatibility events supported for eventNotifications");
+
+ ret = yield front.unregisterEventNotifications({ events: ["console-api-profiler", "profiler-started", "profiler-stopped"] });
+ do_check_true(ret.registered.length === 3);
+});
+
+function getChromeActors() {
+ let deferred = promise.defer();
+ get_chrome_actors((client, form) => deferred.resolve([client, form]));
+ return deferred.promise;
+}
diff --git a/devtools/server/tests/unit/test_profiler_events-02.js b/devtools/server/tests/unit/test_profiler_events-02.js
new file mode 100644
index 000000000..fed702043
--- /dev/null
+++ b/devtools/server/tests/unit/test_profiler_events-02.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests the event notification service for the profiler actor.
+ */
+
+const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+const MAX_PROFILER_ENTRIES = 10000000;
+const { ProfilerFront } = require("devtools/shared/fronts/profiler");
+const { waitForTime } = DevToolsUtils;
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ let [client, form] = yield getChromeActors();
+ let front = new ProfilerFront(client, form);
+
+ // Ensure the profiler is not running when the test starts (it could
+ // happen if the MOZ_PROFILER_STARTUP environment variable is set).
+ Profiler.StopProfiler();
+ let eventsCalled = 0;
+ let handledThreeTimes = promise.defer();
+
+ front.on("profiler-status", (response) => {
+ dump("'profiler-status' fired\n");
+ do_check_true(typeof response.position === "number");
+ do_check_true(typeof response.totalSize === "number");
+ do_check_true(typeof response.generation === "number");
+ do_check_true(response.position > 0 && response.position < response.totalSize);
+ do_check_true(response.totalSize === MAX_PROFILER_ENTRIES);
+ // There's no way we'll fill the buffer in this test.
+ do_check_true(response.generation === 0);
+
+ eventsCalled++;
+ if (eventsCalled > 2) {
+ handledThreeTimes.resolve();
+ }
+ });
+
+ yield front.setProfilerStatusInterval(1);
+ dump("Set the profiler-status event interval to 1\n");
+ yield front.startProfiler();
+ yield waitForTime(500);
+ yield front.stopProfiler();
+
+ do_check_true(eventsCalled === 0, "No 'profiler-status' events should be fired before registering.");
+
+ let ret = yield front.registerEventNotifications({ events: ["profiler-status"] });
+ do_check_true(ret.registered.length === 1);
+
+ yield front.startProfiler();
+ yield handledThreeTimes.promise;
+ yield front.stopProfiler();
+ do_check_true(eventsCalled >= 3, "profiler-status fired atleast three times while recording");
+
+ let totalEvents = eventsCalled;
+ yield waitForTime(50);
+ do_check_true(totalEvents === eventsCalled, "No more profiler-status events after recording.");
+});
+
+function getChromeActors() {
+ let deferred = promise.defer();
+ get_chrome_actors((client, form) => deferred.resolve([client, form]));
+ return deferred.promise;
+}
diff --git a/devtools/server/tests/unit/test_profiler_getbufferinfo.js b/devtools/server/tests/unit/test_profiler_getbufferinfo.js
new file mode 100644
index 000000000..1ec536738
--- /dev/null
+++ b/devtools/server/tests/unit/test_profiler_getbufferinfo.js
@@ -0,0 +1,123 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the profiler actor returns its buffer status via getBufferInfo.
+ */
+
+const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+const INITIAL_WAIT_TIME = 100; // ms
+const MAX_WAIT_TIME = 20000; // ms
+const MAX_PROFILER_ENTRIES = 10000000;
+
+function run_test()
+{
+ // Ensure the profiler is not running when the test starts (it could
+ // happen if the MOZ_PROFILER_STARTUP environment variable is set).
+ Profiler.StopProfiler();
+
+ get_chrome_actors((client, form) => {
+ let actor = form.profilerActor;
+ check_empty_buffer(client, actor, () => {
+ activate_profiler(client, actor, startTime => {
+ wait_for_samples(client, actor, () => {
+ check_buffer(client, actor, () => {
+ deactivate_profiler(client, actor, () => {
+ client.close().then(do_test_finished);
+ });
+ });
+ });
+ });
+ });
+ });
+
+ do_test_pending();
+}
+
+function check_empty_buffer(client, actor, callback)
+{
+ client.request({ to: actor, type: "getBufferInfo" }, response => {
+ do_check_true(response.position === 0);
+ do_check_true(response.totalSize === 0);
+ do_check_true(response.generation === 0);
+ callback();
+ });
+}
+
+function check_buffer(client, actor, callback)
+{
+ client.request({ to: actor, type: "getBufferInfo" }, response => {
+ do_check_true(typeof response.position === "number");
+ do_check_true(typeof response.totalSize === "number");
+ do_check_true(typeof response.generation === "number");
+ do_check_true(response.position > 0 && response.position < response.totalSize);
+ do_check_true(response.totalSize === MAX_PROFILER_ENTRIES);
+ // There's no way we'll fill the buffer in this test.
+ do_check_true(response.generation === 0);
+
+ callback();
+ });
+}
+
+function activate_profiler(client, actor, callback)
+{
+ client.request({ to: actor, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => {
+ do_check_true(response.started);
+ client.request({ to: actor, type: "isActive" }, response => {
+ do_check_true(response.isActive);
+ callback(response.currentTime);
+ });
+ });
+}
+
+function deactivate_profiler(client, actor, callback)
+{
+ client.request({ to: actor, type: "stopProfiler" }, response => {
+ do_check_false(response.started);
+ client.request({ to: actor, type: "isActive" }, response => {
+ do_check_false(response.isActive);
+ callback();
+ });
+ });
+}
+
+function wait_for_samples(client, actor, callback)
+{
+ function attempt(delay)
+ {
+ // No idea why, but Components.stack.sourceLine returns null.
+ let funcLine = Components.stack.lineNumber - 3;
+
+ // Spin for the requested time, then take a sample.
+ let start = Date.now();
+ let stack;
+ do_print("Attempt: delay = " + delay);
+ while (Date.now() - start < delay) { stack = Components.stack; }
+ do_print("Attempt: finished waiting.");
+
+ client.request({ to: actor, type: "getProfile" }, response => {
+ // At this point, we may or may not have samples, depending on
+ // whether the spin loop above has given the profiler enough time
+ // to get started.
+ if (response.profile.threads[0].samples.length == 0) {
+ if (delay < MAX_WAIT_TIME) {
+ // Double the spin-wait time and try again.
+ do_print("Attempt: no samples, going around again.");
+ return attempt(delay * 2);
+ } else {
+ // We've waited long enough, so just fail.
+ do_print("Attempt: waited a long time, but no samples were collected.");
+ do_print("Giving up.");
+ do_check_true(false);
+ return;
+ }
+ }
+ callback();
+ });
+ }
+
+ // Start off with a 100 millisecond delay.
+ attempt(INITIAL_WAIT_TIME);
+}
diff --git a/devtools/server/tests/unit/test_profiler_getfeatures.js b/devtools/server/tests/unit/test_profiler_getfeatures.js
new file mode 100644
index 000000000..5b37e7d55
--- /dev/null
+++ b/devtools/server/tests/unit/test_profiler_getfeatures.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests whether the profiler responds to "getFeatures" adequately.
+ */
+
+const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+
+function run_test()
+{
+ get_chrome_actors((client, form) => {
+ let actor = form.profilerActor;
+ test_getfeatures(client, actor, () => {
+ client.close().then(() => {
+ do_test_finished();
+ });
+ });
+ });
+
+ do_test_pending();
+}
+
+function test_getfeatures(client, actor, callback)
+{
+ client.request({ to: actor, type: "getFeatures" }, response => {
+ do_check_eq(typeof response.features, "object");
+ do_check_true(response.features.length >= 1);
+ do_check_eq(typeof response.features[0], "string");
+ do_check_true(response.features.indexOf("js") != -1);
+ callback();
+ });
+}
diff --git a/devtools/server/tests/unit/test_profiler_getsharedlibraryinformation.js b/devtools/server/tests/unit/test_profiler_getsharedlibraryinformation.js
new file mode 100644
index 000000000..a36577320
--- /dev/null
+++ b/devtools/server/tests/unit/test_profiler_getsharedlibraryinformation.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests whether the profiler responds to "getSharedLibraryInformation" adequately.
+ */
+
+const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+
+function run_test()
+{
+ get_chrome_actors((client, form) => {
+ let actor = form.profilerActor;
+ test_getsharedlibraryinformation(client, actor, () => {
+ client.close().then(() => {
+ do_test_finished();
+ });
+ });
+ });
+
+ do_test_pending();
+}
+
+function test_getsharedlibraryinformation(client, actor, callback)
+{
+ client.request({ to: actor, type: "getSharedLibraryInformation" }, response => {
+ do_check_eq(typeof response.sharedLibraryInformation, "string");
+ let libs = [];
+ try {
+ libs = JSON.parse(response.sharedLibraryInformation);
+ } catch (e) {
+ do_check_true(false);
+ }
+ do_check_eq(typeof libs, "object");
+ do_check_true(libs.length >= 1);
+ do_check_eq(typeof libs[0], "object");
+ do_check_eq(typeof libs[0].name, "string");
+ do_check_eq(typeof libs[0].start, "number");
+ do_check_eq(typeof libs[0].end, "number");
+ do_check_true(libs[0].start <= libs[0].end);
+ callback();
+ });
+}
diff --git a/devtools/server/tests/unit/test_promise_state-01.js b/devtools/server/tests/unit/test_promise_state-01.js
new file mode 100644
index 000000000..a525560ab
--- /dev/null
+++ b/devtools/server/tests/unit/test_promise_state-01.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that the preview in a Promise's grip is correct when the Promise is
+ * pending.
+ */
+
+function run_test()
+{
+ initTestDebuggerServer();
+ const debuggee = addTestGlobal("test-promise-state");
+ const client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function () {
+ attachTestTabAndResume(client, "test-promise-state", function (response, tabClient, threadClient) {
+ Task.spawn(function* () {
+ const packet = yield executeOnNextTickAndWaitForPause(() => evalCode(debuggee), client);
+
+ const grip = packet.frame.environment.bindings.variables.p;
+ ok(grip.value.preview);
+ equal(grip.value.class, "Promise");
+ equal(grip.value.promiseState.state, "pending");
+
+ finishClient(client);
+ });
+ });
+ });
+ do_test_pending();
+}
+
+function evalCode(debuggee) {
+ Components.utils.evalInSandbox(
+ "doTest();\n" +
+ function doTest() {
+ var p = new Promise(function () {});
+ debugger;
+ },
+ debuggee
+ );
+}
diff --git a/devtools/server/tests/unit/test_promise_state-02.js b/devtools/server/tests/unit/test_promise_state-02.js
new file mode 100644
index 000000000..cf44f1946
--- /dev/null
+++ b/devtools/server/tests/unit/test_promise_state-02.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that the preview in a Promise's grip is correct when the Promise is
+ * fulfilled.
+ */
+
+function run_test()
+{
+ initTestDebuggerServer();
+ const debuggee = addTestGlobal("test-promise-state");
+ const client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function () {
+ attachTestTabAndResume(client, "test-promise-state", function (response, tabClient, threadClient) {
+ Task.spawn(function* () {
+ const packet = yield executeOnNextTickAndWaitForPause(() => evalCode(debuggee), client);
+
+ const grip = packet.frame.environment.bindings.variables.p;
+ ok(grip.value.preview);
+ equal(grip.value.class, "Promise");
+ equal(grip.value.promiseState.state, "fulfilled");
+ equal(grip.value.promiseState.value.actorID, packet.frame.arguments[0].actorID,
+ "The promise's fulfilled state value should be the same value passed to the then function");
+
+ finishClient(client);
+ });
+ });
+ });
+ do_test_pending();
+}
+
+function evalCode(debuggee) {
+ Components.utils.evalInSandbox(
+ "doTest();\n" +
+ function doTest() {
+ var resolved = Promise.resolve({});
+ resolved.then(() => {
+ var p = resolved;
+ debugger;
+ });
+ },
+ debuggee
+ );
+}
diff --git a/devtools/server/tests/unit/test_promise_state-03.js b/devtools/server/tests/unit/test_promise_state-03.js
new file mode 100644
index 000000000..cf64e3e27
--- /dev/null
+++ b/devtools/server/tests/unit/test_promise_state-03.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that the preview in a Promise's grip is correct when the Promise is
+ * rejected.
+ */
+
+function run_test()
+{
+ initTestDebuggerServer();
+ const debuggee = addTestGlobal("test-promise-state");
+ const client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function () {
+ attachTestTabAndResume(client, "test-promise-state", function (response, tabClient, threadClient) {
+ Task.spawn(function* () {
+ const packet = yield executeOnNextTickAndWaitForPause(() => evalCode(debuggee), client);
+
+ const grip = packet.frame.environment.bindings.variables.p;
+ ok(grip.value.preview);
+ equal(grip.value.class, "Promise");
+ equal(grip.value.promiseState.state, "rejected");
+ equal(grip.value.promiseState.reason.actorID, packet.frame.arguments[0].actorID,
+ "The promise's rejected state reason should be the same value passed to the then function");
+
+ finishClient(client);
+ });
+ });
+ });
+ do_test_pending();
+}
+
+function evalCode(debuggee) {
+ Components.utils.evalInSandbox(
+ "doTest();\n" +
+ function doTest() {
+ var resolved = Promise.reject(new Error("uh oh"));
+ resolved.then(null, () => {
+ var p = resolved;
+ debugger;
+ });
+ },
+ debuggee
+ );
+}
diff --git a/devtools/server/tests/unit/test_promises_actor_attach.js b/devtools/server/tests/unit/test_promises_actor_attach.js
new file mode 100644
index 000000000..17c2a1f41
--- /dev/null
+++ b/devtools/server/tests/unit/test_promises_actor_attach.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can attach and detach to the PromisesActor under the correct
+ * states.
+ */
+
+const { PromisesFront } = require("devtools/shared/fronts/promises");
+
+add_task(function* () {
+ let client = yield startTestDebuggerServer("promises-actor-test");
+ let chromeActors = yield getChromeActors(client);
+
+ // We have to attach the chrome TabActor before playing with the PromiseActor
+ yield attachTab(client, chromeActors);
+ yield testAttach(client, chromeActors);
+
+ let response = yield listTabs(client);
+ let targetTab = findTab(response.tabs, "promises-actor-test");
+ ok(targetTab, "Found our target tab.");
+
+ let [ tabResponse ] = yield attachTab(client, targetTab);
+
+ yield testAttach(client, tabResponse);
+
+ yield close(client);
+});
+
+function* testAttach(client, parent) {
+ let promises = PromisesFront(client, parent);
+
+ try {
+ yield promises.detach();
+ ok(false, "Should not be able to detach when in a detached state.");
+ } catch (e) {
+ ok(true, "Expected detach to fail when already in a detached state.");
+ }
+
+ yield promises.attach();
+ ok(true, "Expected attach to succeed.");
+
+ try {
+ yield promises.attach();
+ ok(false, "Should not be able to attach when in an attached state.");
+ } catch (e) {
+ ok(true, "Expected attach to fail when already in an attached state.");
+ }
+
+ yield promises.detach();
+ ok(true, "Expected detach to succeed.");
+}
diff --git a/devtools/server/tests/unit/test_promises_actor_exist.js b/devtools/server/tests/unit/test_promises_actor_exist.js
new file mode 100644
index 000000000..13eef3e99
--- /dev/null
+++ b/devtools/server/tests/unit/test_promises_actor_exist.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that the PromisesActor exists in the TabActors and ChromeActors.
+ */
+
+add_task(function* () {
+ let client = yield startTestDebuggerServer("promises-actor-test");
+
+ let response = yield listTabs(client);
+ let targetTab = findTab(response.tabs, "promises-actor-test");
+ ok(targetTab, "Found our target tab.");
+
+ // Attach to the TabActor and check the response
+ client.request({ to: targetTab.actor, type: "attach" }, response => {
+ ok(!("error" in response), "Expect no error in response.");
+ ok(response.from, targetTab.actor,
+ "Expect the target TabActor in response form field.");
+ ok(response.type, "tabAttached",
+ "Expect tabAttached in the response type.");
+ is(typeof response.promisesActor === "string",
+ "Should have a tab context PromisesActor.");
+ });
+
+ let chromeActors = yield getChromeActors(client);
+ ok(typeof chromeActors.promisesActor === "string",
+ "Should have a chrome context PromisesActor.");
+});
diff --git a/devtools/server/tests/unit/test_promises_actor_list_promises.js b/devtools/server/tests/unit/test_promises_actor_list_promises.js
new file mode 100644
index 000000000..f5b273121
--- /dev/null
+++ b/devtools/server/tests/unit/test_promises_actor_list_promises.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can get the list of all live Promise objects from the
+ * PromisesActor.
+ */
+
+"use strict";
+
+const { PromisesFront } = require("devtools/shared/fronts/promises");
+const SECRET = "MyLittleSecret";
+
+add_task(function* () {
+ let client = yield startTestDebuggerServer("promises-actor-test");
+ let chromeActors = yield getChromeActors(client);
+
+ // We have to attach the chrome TabActor before playing with the PromiseActor
+ yield attachTab(client, chromeActors);
+ yield testListPromises(client, chromeActors, v =>
+ new Promise(resolve => resolve(v)));
+
+ let response = yield listTabs(client);
+ let targetTab = findTab(response.tabs, "promises-actor-test");
+ ok(targetTab, "Found our target tab.");
+
+ yield testListPromises(client, targetTab, v => {
+ const debuggee = DebuggerServer.getTestGlobal("promises-actor-test");
+ return debuggee.Promise.resolve(v);
+ });
+
+ yield close(client);
+});
+
+function* testListPromises(client, form, makePromise) {
+ let resolution = SECRET + Math.random();
+ let promise = makePromise(resolution);
+ let front = PromisesFront(client, form);
+
+ yield front.attach();
+
+ let promises = yield front.listPromises();
+
+ let found = false;
+ for (let p of promises) {
+ equal(p.type, "object", "Expect type to be Object");
+ equal(p.class, "Promise", "Expect class to be Promise");
+ equal(typeof p.promiseState.creationTimestamp, "number",
+ "Expect creation timestamp to be a number");
+ equal(typeof p.promiseState.timeToSettle, "number",
+ "Expect time to settle to be a number");
+
+ if (p.promiseState.state === "fulfilled" &&
+ p.promiseState.value === resolution) {
+ found = true;
+ }
+ }
+
+ ok(found, "Found our promise");
+ yield front.detach();
+ // Appease eslint
+ void promise;
+}
diff --git a/devtools/server/tests/unit/test_promises_actor_onnewpromise.js b/devtools/server/tests/unit/test_promises_actor_onnewpromise.js
new file mode 100644
index 000000000..04b3e6510
--- /dev/null
+++ b/devtools/server/tests/unit/test_promises_actor_onnewpromise.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can get the list of all new Promise objects from the
+ * PromisesActor onNewPromise event handler.
+ */
+
+"use strict";
+
+const { PromisesFront } = require("devtools/shared/fronts/promises");
+
+var events = require("sdk/event/core");
+
+add_task(function* () {
+ let client = yield startTestDebuggerServer("promises-actor-test");
+ let chromeActors = yield getChromeActors(client);
+
+ ok(Promise.toString().includes("native code"), "Expect native DOM Promise");
+
+ // We have to attach the chrome TabActor before playing with the PromiseActor
+ yield attachTab(client, chromeActors);
+ yield testNewPromisesEvent(client, chromeActors,
+ v => new Promise(resolve => resolve(v)));
+
+ let response = yield listTabs(client);
+ let targetTab = findTab(response.tabs, "promises-actor-test");
+ ok(targetTab, "Found our target tab.");
+
+ yield testNewPromisesEvent(client, targetTab, v => {
+ const debuggee = DebuggerServer.getTestGlobal("promises-actor-test");
+ return debuggee.Promise.resolve(v);
+ });
+
+ yield close(client);
+});
+
+function* testNewPromisesEvent(client, form, makePromise) {
+ let front = PromisesFront(client, form);
+ let resolution = "MyLittleSecret" + Math.random();
+ let found = false;
+
+ yield front.attach();
+ yield front.listPromises();
+
+ let onNewPromise = new Promise(resolve => {
+ events.on(front, "new-promises", promises => {
+ for (let p of promises) {
+ equal(p.type, "object", "Expect type to be Object");
+ equal(p.class, "Promise", "Expect class to be Promise");
+ equal(typeof p.promiseState.creationTimestamp, "number",
+ "Expect creation timestamp to be a number");
+
+ if (p.promiseState.state === "fulfilled" &&
+ p.promiseState.value === resolution) {
+ found = true;
+ resolve();
+ } else {
+ dump("Found non-target promise\n");
+ }
+ }
+ });
+ });
+
+ let promise = makePromise(resolution);
+
+ yield onNewPromise;
+ ok(found, "Found our new promise");
+ yield front.detach();
+ // Appease eslint
+ void promise;
+}
diff --git a/devtools/server/tests/unit/test_promises_actor_onpromisesettled.js b/devtools/server/tests/unit/test_promises_actor_onpromisesettled.js
new file mode 100644
index 000000000..ab4774733
--- /dev/null
+++ b/devtools/server/tests/unit/test_promises_actor_onpromisesettled.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can get the list of Promise objects that have settled from the
+ * PromisesActor onPromiseSettled event handler.
+ */
+
+"use strict";
+
+Cu.import("resource://testing-common/PromiseTestUtils.jsm", this);
+
+const { PromisesFront } = require("devtools/shared/fronts/promises");
+
+var events = require("sdk/event/core");
+
+add_task(function* () {
+ let client = yield startTestDebuggerServer("promises-actor-test");
+ let chromeActors = yield getChromeActors(client);
+
+ ok(Promise.toString().includes("native code"), "Expect native DOM Promise");
+
+ // We have to attach the chrome TabActor before playing with the PromiseActor
+ yield attachTab(client, chromeActors);
+ yield testPromisesSettled(client, chromeActors,
+ v => new Promise(resolve => resolve(v)),
+ v => new Promise((resolve, reject) => reject(v)));
+
+ let response = yield listTabs(client);
+ let targetTab = findTab(response.tabs, "promises-actor-test");
+ ok(targetTab, "Found our target tab.");
+
+ yield testPromisesSettled(client, targetTab, v => {
+ const debuggee = DebuggerServer.getTestGlobal("promises-actor-test");
+ return debuggee.Promise.resolve(v);
+ }, v => {
+ const debuggee = DebuggerServer.getTestGlobal("promises-actor-test");
+ return debuggee.Promise.reject(v);
+ });
+
+ yield close(client);
+});
+
+function* testPromisesSettled(client, form, makeResolvePromise,
+ makeRejectPromise) {
+ let front = PromisesFront(client, form);
+ let resolution = "MyLittleSecret" + Math.random();
+
+ yield front.attach();
+ yield front.listPromises();
+
+ let onPromiseSettled = oncePromiseSettled(front, resolution, true, false);
+ let resolvedPromise = makeResolvePromise(resolution);
+ let foundResolvedPromise = yield onPromiseSettled;
+ ok(foundResolvedPromise, "Found our resolved promise");
+
+ PromiseTestUtils.expectUncaughtRejection(r => r.message == resolution);
+ onPromiseSettled = oncePromiseSettled(front, resolution, false, true);
+ let rejectedPromise = makeRejectPromise(resolution);
+ let foundRejectedPromise = yield onPromiseSettled;
+ ok(foundRejectedPromise, "Found our rejected promise");
+
+ yield front.detach();
+ // Appease eslint
+ void resolvedPromise;
+ void rejectedPromise;
+}
+
+function oncePromiseSettled(front, resolution, resolveValue, rejectValue) {
+ return new Promise(resolve => {
+ events.on(front, "promises-settled", promises => {
+ for (let p of promises) {
+ equal(p.type, "object", "Expect type to be Object");
+ equal(p.class, "Promise", "Expect class to be Promise");
+ equal(typeof p.promiseState.creationTimestamp, "number",
+ "Expect creation timestamp to be a number");
+ equal(typeof p.promiseState.timeToSettle, "number",
+ "Expect time to settle to be a number");
+
+ if (p.promiseState.state === "fulfilled" &&
+ p.promiseState.value === resolution) {
+ resolve(resolveValue);
+ } else if (p.promiseState.state === "rejected" &&
+ p.promiseState.reason === resolution) {
+ resolve(rejectValue);
+ } else {
+ dump("Found non-target promise\n");
+ }
+ }
+ });
+ });
+}
diff --git a/devtools/server/tests/unit/test_promises_client_getdependentpromises.js b/devtools/server/tests/unit/test_promises_client_getdependentpromises.js
new file mode 100644
index 000000000..8900cf81c
--- /dev/null
+++ b/devtools/server/tests/unit/test_promises_client_getdependentpromises.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can get the list of dependent promises from the ObjectClient.
+ */
+
+"use strict";
+
+const { PromisesFront } = require("devtools/shared/fronts/promises");
+
+var events = require("sdk/event/core");
+
+add_task(function* () {
+ let client = yield startTestDebuggerServer("test-promises-dependentpromises");
+ let chromeActors = yield getChromeActors(client);
+ yield attachTab(client, chromeActors);
+
+ ok(Promise.toString().includes("native code"), "Expect native DOM Promise.");
+
+ yield testGetDependentPromises(client, chromeActors, () => {
+ let p = new Promise(() => {});
+ p.name = "p";
+ let q = p.then();
+ q.name = "q";
+ let r = p.then(null, () => {});
+ r.name = "r";
+
+ return p;
+ });
+
+ let response = yield listTabs(client);
+ let targetTab = findTab(response.tabs, "test-promises-dependentpromises");
+ ok(targetTab, "Found our target tab.");
+ yield attachTab(client, targetTab);
+
+ yield testGetDependentPromises(client, targetTab, () => {
+ const debuggee =
+ DebuggerServer.getTestGlobal("test-promises-dependentpromises");
+
+ let p = new debuggee.Promise(() => {});
+ p.name = "p";
+ let q = p.then();
+ q.name = "q";
+ let r = p.then(null, () => {});
+ r.name = "r";
+
+ return p;
+ });
+
+ yield close(client);
+});
+
+function* testGetDependentPromises(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);
+ }
+ }
+ });
+ });
+
+ let promise = makePromises();
+
+ let grip = yield onNewPromise;
+ ok(grip, "Found our promise p.");
+
+ let objectClient = new ObjectClient(client, grip);
+ ok(objectClient, "Got Object Client.");
+
+ // Get the dependent promises for promise p and assert that the list of
+ // dependent promises is correct
+ yield new Promise(resolve => {
+ objectClient.getDependentPromises(response => {
+ let dependentNames = response.promises.map(p =>
+ p.preview.ownProperties.name.value);
+ let expectedDependentNames = ["q", "r"];
+
+ equal(dependentNames.length, expectedDependentNames.length,
+ "Got expected number of dependent promises.");
+
+ for (let i = 0; i < dependentNames.length; i++) {
+ equal(dependentNames[i], expectedDependentNames[i],
+ "Got expected dependent name.");
+ }
+
+ for (let p of response.promises) {
+ equal(p.type, "object", "Expect type to be Object.");
+ equal(p.class, "Promise", "Expect class to be Promise.");
+ equal(typeof p.promiseState.creationTimestamp, "number",
+ "Expect creation timestamp to be a number.");
+ ok(!p.promiseState.timeToSettle,
+ "Expect time to settle to be undefined.");
+ }
+
+ resolve();
+ });
+ });
+
+ yield front.detach();
+ // Appease eslint
+ void promise;
+}
diff --git a/devtools/server/tests/unit/test_promises_object_creationtimestamp.js b/devtools/server/tests/unit/test_promises_object_creationtimestamp.js
new file mode 100644
index 000000000..1360be56a
--- /dev/null
+++ b/devtools/server/tests/unit/test_promises_object_creationtimestamp.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get the approximate time range for promise creation timestamp.
+ */
+
+"use strict";
+
+const { PromisesFront } = require("devtools/shared/fronts/promises");
+
+var events = require("sdk/event/core");
+
+add_task(function* () {
+ let client = yield startTestDebuggerServer("promises-object-test");
+ let chromeActors = yield getChromeActors(client);
+
+ ok(Promise.toString().includes("native code"), "Expect native DOM Promise.");
+
+ // We have to attach the chrome TabActor before playing with the PromiseActor
+ yield attachTab(client, chromeActors);
+ yield testPromiseCreationTimestamp(client, chromeActors, v => {
+ return new Promise(resolve => resolve(v));
+ });
+
+ let response = yield listTabs(client);
+ let targetTab = findTab(response.tabs, "promises-object-test");
+ ok(targetTab, "Found our target tab.");
+
+ yield testPromiseCreationTimestamp(client, targetTab, v => {
+ const debuggee = DebuggerServer.getTestGlobal("promises-object-test");
+ return debuggee.Promise.resolve(v);
+ });
+
+ yield close(client);
+});
+
+function* testPromiseCreationTimestamp(client, form, makePromise) {
+ let front = PromisesFront(client, form);
+ let resolution = "MyLittleSecret" + Math.random();
+
+ yield front.attach();
+ yield front.listPromises();
+
+ let onNewPromise = new Promise(resolve => {
+ events.on(front, "new-promises", promises => {
+ for (let p of promises) {
+ if (p.promiseState.state === "fulfilled" &&
+ p.promiseState.value === resolution) {
+ resolve(p);
+ }
+ }
+ });
+ });
+
+ let start = Date.now();
+ let promise = makePromise(resolution);
+ let end = Date.now();
+
+ let grip = yield onNewPromise;
+ ok(grip, "Found our new promise.");
+
+ let creationTimestamp = grip.promiseState.creationTimestamp;
+
+ ok(start - 1 <= creationTimestamp && creationTimestamp <= end + 1,
+ "Expect promise creation timestamp to be within elapsed time range.");
+
+ yield front.detach();
+ // Appease eslint
+ void promise;
+}
diff --git a/devtools/server/tests/unit/test_promises_object_timetosettle-01.js b/devtools/server/tests/unit/test_promises_object_timetosettle-01.js
new file mode 100644
index 000000000..1b3240e3d
--- /dev/null
+++ b/devtools/server/tests/unit/test_promises_object_timetosettle-01.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test whether or not we get the time to settle depending on the state of the
+ * promise.
+ */
+
+"use strict";
+
+const { PromisesFront } = require("devtools/shared/fronts/promises");
+
+var events = require("sdk/event/core");
+
+add_task(function* () {
+ let client = yield startTestDebuggerServer("test-promises-timetosettle");
+ let chromeActors = yield getChromeActors(client);
+
+ ok(Promise.toString().includes("native code"), "Expect native DOM Promise.");
+
+ // We have to attach the chrome TabActor before playing with the PromiseActor
+ yield attachTab(client, chromeActors);
+ yield testGetTimeToSettle(client, chromeActors, () => {
+ let p = new Promise(() => {});
+ p.name = "p";
+ let q = p.then();
+ q.name = "q";
+
+ return p;
+ });
+
+ let response = yield listTabs(client);
+ let targetTab = findTab(response.tabs, "test-promises-timetosettle");
+ ok(targetTab, "Found our target tab.");
+
+ yield testGetTimeToSettle(client, targetTab, () => {
+ const debuggee =
+ DebuggerServer.getTestGlobal("test-promises-timetosettle");
+
+ let p = new debuggee.Promise(() => {});
+ p.name = "p";
+ let q = p.then();
+ q.name = "q";
+
+ return p;
+ });
+
+ yield close(client);
+});
+
+function* testGetTimeToSettle(client, form, makePromises) {
+ let front = PromisesFront(client, form);
+
+ yield front.attach();
+ yield front.listPromises();
+
+ let onNewPromise = new Promise(resolve => {
+ events.on(front, "new-promises", promises => {
+ for (let p of promises) {
+ if (p.promiseState.state === "pending") {
+ ok(!p.promiseState.timeToSettle,
+ "Expect no time to settle for unsettled promise.");
+ } else {
+ ok(p.promiseState.timeToSettle,
+ "Expect time to settle for settled promise.");
+ equal(typeof p.promiseState.timeToSettle, "number",
+ "Expect time to settle to be a number.");
+ }
+ }
+ resolve();
+ });
+ });
+
+ let promise = makePromises();
+
+ yield onNewPromise;
+ yield front.detach();
+ // Appease eslint
+ void promise;
+}
diff --git a/devtools/server/tests/unit/test_promises_object_timetosettle-02.js b/devtools/server/tests/unit/test_promises_object_timetosettle-02.js
new file mode 100644
index 000000000..10224d0b9
--- /dev/null
+++ b/devtools/server/tests/unit/test_promises_object_timetosettle-02.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get the expected settlement time for promise time to settle.
+ */
+
+"use strict";
+
+const { PromisesFront } = require("devtools/shared/fronts/promises");
+const { setTimeout } = require("sdk/timers");
+
+var events = require("sdk/event/core");
+
+add_task(function* () {
+ let client = yield startTestDebuggerServer("test-promises-timetosettle");
+ let chromeActors = yield getChromeActors(client);
+ yield attachTab(client, chromeActors);
+
+ ok(Promise.toString().includes("native code"), "Expect native DOM Promise.");
+
+ // We have to attach the chrome TabActor before playing with the PromiseActor
+ yield attachTab(client, chromeActors);
+ yield testGetTimeToSettle(client, chromeActors,
+ v => new Promise(resolve => setTimeout(() => resolve(v), 100)));
+
+ let response = yield listTabs(client);
+ let targetTab = findTab(response.tabs, "test-promises-timetosettle");
+ ok(targetTab, "Found our target tab.");
+ yield attachTab(client, targetTab);
+
+ yield testGetTimeToSettle(client, targetTab, v => {
+ const debuggee =
+ DebuggerServer.getTestGlobal("test-promises-timetosettle");
+ return new debuggee.Promise(resolve => setTimeout(() => resolve(v), 100));
+ });
+
+ yield close(client);
+});
+
+function* testGetTimeToSettle(client, form, makePromise) {
+ let front = PromisesFront(client, form);
+ let resolution = "MyLittleSecret" + Math.random();
+ let found = false;
+
+ yield front.attach();
+ yield front.listPromises();
+
+ let onNewPromise = new Promise(resolve => {
+ events.on(front, "promises-settled", promises => {
+ for (let p of promises) {
+ if (p.promiseState.state === "fulfilled" &&
+ p.promiseState.value === resolution) {
+ let timeToSettle = Math.floor(p.promiseState.timeToSettle / 100) * 100;
+ ok(timeToSettle >= 100,
+ "Expect time to settle for resolved promise to be " +
+ "at least 100ms, got " + timeToSettle + "ms.");
+ found = true;
+ resolve();
+ } else {
+ dump("Found non-target promise.\n");
+ }
+ }
+ });
+ });
+
+ let promise = makePromise(resolution);
+
+ yield onNewPromise;
+ ok(found, "Found our new promise.");
+ yield front.detach();
+ // Appease eslint
+ void promise;
+}
diff --git a/devtools/server/tests/unit/test_protocolSpec.js b/devtools/server/tests/unit/test_protocolSpec.js
new file mode 100644
index 000000000..cc0746387
--- /dev/null
+++ b/devtools/server/tests/unit/test_protocolSpec.js
@@ -0,0 +1,17 @@
+const run_test = Test(function* () {
+ initTestDebuggerServer();
+ const connection = DebuggerServer.connectPipe();
+ const client = Async(new DebuggerClient(connection));
+
+ yield client.connect();
+
+ const response = yield client.request({
+ to: "root",
+ type: "protocolDescription"
+ });
+
+ assert(response.from == "root");
+ assert(typeof (response.types) === "object");
+
+ yield client.close();
+});
diff --git a/devtools/server/tests/unit/test_protocol_abort.js b/devtools/server/tests/unit/test_protocol_abort.js
new file mode 100644
index 000000000..bb25d1b2c
--- /dev/null
+++ b/devtools/server/tests/unit/test_protocol_abort.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Outstanding requests should be rejected when the connection aborts
+ * unexpectedly.
+ */
+
+var protocol = require("devtools/shared/protocol");
+var {Arg, Option, RetVal} = protocol;
+var events = require("sdk/event/core");
+
+function simpleHello() {
+ return {
+ from: "root",
+ applicationType: "xpcshell-tests",
+ traits: [],
+ };
+}
+
+const rootSpec = protocol.generateActorSpec({
+ typeName: "root",
+
+ methods: {
+ simpleReturn: {
+ response: { value: RetVal() }
+ }
+ }
+});
+
+var RootActor = protocol.ActorClassWithSpec(rootSpec, {
+ typeName: "root",
+ initialize: function (conn) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ // Root actor owns itself.
+ this.manage(this);
+ this.actorID = "root";
+ this.sequence = 0;
+ },
+
+ sayHello: simpleHello,
+
+ simpleReturn: function () {
+ return this.sequence++;
+ }
+});
+
+var RootFront = protocol.FrontClassWithSpec(rootSpec, {
+ initialize: function (client) {
+ this.actorID = "root";
+ protocol.Front.prototype.initialize.call(this, client);
+ // Root owns itself.
+ this.manage(this);
+ }
+});
+
+function run_test() {
+ DebuggerServer.createRootActor = RootActor;
+ DebuggerServer.init();
+
+ let trace = connectPipeTracing();
+ let client = new DebuggerClient(trace);
+ let rootClient;
+
+ client.connect().then(([applicationType, traits]) => {
+ rootClient = RootFront(client);
+
+ rootClient.simpleReturn().then(() => {
+ ok(false, "Connection was aborted, request shouldn't resolve");
+ do_test_finished();
+ }, e => {
+ let error = e.toString();
+ ok(true, "Connection was aborted, request rejected correctly");
+ ok(error.includes("Request stack:"), "Error includes request stack");
+ ok(error.includes("test_protocol_abort.js"), "Stack includes this test");
+ do_test_finished();
+ });
+
+ trace.close();
+ });
+
+ do_test_pending();
+}
diff --git a/devtools/server/tests/unit/test_protocol_async.js b/devtools/server/tests/unit/test_protocol_async.js
new file mode 100644
index 000000000..75f053863
--- /dev/null
+++ b/devtools/server/tests/unit/test_protocol_async.js
@@ -0,0 +1,184 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure we get replies in the same order that we sent their
+ * requests even when earlier requests take several event ticks to
+ * complete.
+ */
+
+var protocol = require("devtools/shared/protocol");
+var {Arg, Option, RetVal} = protocol;
+var events = require("sdk/event/core");
+
+function simpleHello() {
+ return {
+ from: "root",
+ applicationType: "xpcshell-tests",
+ traits: [],
+ };
+}
+
+const rootSpec = protocol.generateActorSpec({
+ typeName: "root",
+
+ methods: {
+ simpleReturn: {
+ response: { value: RetVal() },
+ },
+ promiseReturn: {
+ request: { toWait: Arg(0, "number") },
+ response: { value: RetVal("number") },
+ },
+ simpleThrow: {
+ response: { value: RetVal("number") }
+ },
+ promiseThrow: {
+ response: { value: RetVal("number") },
+ }
+ }
+});
+
+var RootActor = protocol.ActorClassWithSpec(rootSpec, {
+ initialize: function (conn) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ // Root actor owns itself.
+ this.manage(this);
+ this.actorID = "root";
+ this.sequence = 0;
+ },
+
+ sayHello: simpleHello,
+
+ simpleReturn: function () {
+ return this.sequence++;
+ },
+
+ promiseReturn: function (toWait) {
+ // Guarantee that this resolves after simpleReturn returns.
+ let deferred = promise.defer();
+ let sequence = this.sequence++;
+
+ // Wait until the number of requests specified by toWait have
+ // happened, to test queuing.
+ let check = () => {
+ if ((this.sequence - sequence) < toWait) {
+ do_execute_soon(check);
+ return;
+ }
+ deferred.resolve(sequence);
+ };
+ do_execute_soon(check);
+
+ return deferred.promise;
+ },
+
+ simpleThrow: function () {
+ throw new Error(this.sequence++);
+ },
+
+ promiseThrow: function () {
+ // Guarantee that this resolves after simpleReturn returns.
+ let deferred = promise.defer();
+ let sequence = this.sequence++;
+ // This should be enough to force a failure if the code is broken.
+ do_timeout(150, () => {
+ deferred.reject(sequence++);
+ });
+ return deferred.promise;
+ }
+});
+
+var RootFront = protocol.FrontClassWithSpec(rootSpec, {
+ initialize: function (client) {
+ this.actorID = "root";
+ protocol.Front.prototype.initialize.call(this, client);
+ // Root owns itself.
+ this.manage(this);
+ }
+});
+
+function run_test()
+{
+ DebuggerServer.createRootActor = RootActor;
+ DebuggerServer.init();
+
+ let trace = connectPipeTracing();
+ let client = new DebuggerClient(trace);
+ let rootClient;
+
+ client.connect().then(([applicationType, traits]) => {
+ rootClient = RootFront(client);
+
+ let calls = [];
+ let sequence = 0;
+
+ // Execute a call that won't finish processing until 2
+ // more calls have happened
+ calls.push(rootClient.promiseReturn(2).then(ret => {
+ do_check_eq(sequence, 0); // Check right return order
+ do_check_eq(ret, sequence++); // Check request handling order
+ }));
+
+ // Put a few requests into the backlog
+
+ calls.push(rootClient.simpleReturn().then(ret => {
+ do_check_eq(sequence, 1); // Check right return order
+ do_check_eq(ret, sequence++); // Check request handling order
+ }));
+
+ calls.push(rootClient.simpleReturn().then(ret => {
+ do_check_eq(sequence, 2); // Check right return order
+ do_check_eq(ret, sequence++); // Check request handling order
+ }));
+
+ calls.push(rootClient.simpleThrow().then(() => {
+ do_check_true(false, "simpleThrow shouldn't succeed!");
+ }, error => {
+ do_check_eq(sequence++, 3); // Check right return order
+ }));
+
+ // While packets are sent in the correct order, rejection handlers
+ // registered in "Promise.jsm" may be invoked later than fulfillment
+ // handlers, meaning that we can't check the actual order with certainty.
+ let deferAfterRejection = promise.defer();
+
+ calls.push(rootClient.promiseThrow().then(() => {
+ do_check_true(false, "promiseThrow shouldn't succeed!");
+ }, error => {
+ do_check_eq(sequence++, 4); // Check right return order
+ do_check_true(true, "simple throw should throw");
+ deferAfterRejection.resolve();
+ }));
+
+ calls.push(rootClient.simpleReturn().then(ret => {
+ return deferAfterRejection.promise.then(function () {
+ do_check_eq(sequence, 5); // Check right return order
+ do_check_eq(ret, sequence++); // Check request handling order
+ });
+ }));
+
+ // Break up the backlog with a long request that waits
+ // for another simpleReturn before completing
+ calls.push(rootClient.promiseReturn(1).then(ret => {
+ return deferAfterRejection.promise.then(function () {
+ do_check_eq(sequence, 6); // Check right return order
+ do_check_eq(ret, sequence++); // Check request handling order
+ });
+ }));
+
+ calls.push(rootClient.simpleReturn().then(ret => {
+ return deferAfterRejection.promise.then(function () {
+ do_check_eq(sequence, 7); // Check right return order
+ do_check_eq(ret, sequence++); // Check request handling order
+ });
+ }));
+
+ promise.all(calls).then(() => {
+ client.close().then(() => {
+ do_test_finished();
+ });
+ });
+ });
+ do_test_pending();
+}
diff --git a/devtools/server/tests/unit/test_protocol_children.js b/devtools/server/tests/unit/test_protocol_children.js
new file mode 100644
index 000000000..67773ebef
--- /dev/null
+++ b/devtools/server/tests/unit/test_protocol_children.js
@@ -0,0 +1,559 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test simple requests using the protocol helpers.
+ */
+var protocol = require("devtools/shared/protocol");
+var {preEvent, types, Arg, Option, RetVal} = protocol;
+
+var events = require("sdk/event/core");
+
+function simpleHello() {
+ return {
+ from: "root",
+ applicationType: "xpcshell-tests",
+ traits: [],
+ };
+}
+
+var testTypes = {};
+
+// Predeclaring the actor type so that it can be used in the
+// implementation of the child actor.
+types.addActorType("childActor");
+
+const childSpec = protocol.generateActorSpec({
+ typeName: "childActor",
+
+ events: {
+ "event1" : {
+ a: Arg(0),
+ b: Arg(1),
+ c: Arg(2)
+ },
+ "event2" : {
+ a: Arg(0),
+ b: Arg(1),
+ c: Arg(2)
+ },
+ "named-event": {
+ type: "namedEvent",
+ a: Arg(0),
+ b: Arg(1),
+ c: Arg(2)
+ },
+ "object-event": {
+ type: "objectEvent",
+ detail: Arg(0, "childActor#detail1"),
+ },
+ "array-object-event": {
+ type: "arrayObjectEvent",
+ detail: Arg(0, "array:childActor#detail2"),
+ }
+ },
+
+ methods: {
+ echo: {
+ request: { str: Arg(0) },
+ response: { str: RetVal("string") },
+ },
+ getDetail1: {
+ // This also exercises return-value-as-packet.
+ response: RetVal("childActor#detail1"),
+ },
+ getDetail2: {
+ // This also exercises return-value-as-packet.
+ response: RetVal("childActor#detail2"),
+ },
+ getIDDetail: {
+ response: {
+ idDetail: RetVal("childActor#actorid")
+ }
+ },
+ getIntArray: {
+ request: { inputArray: Arg(0, "array:number") },
+ response: RetVal("array:number")
+ },
+ getSibling: {
+ request: { id: Arg(0) },
+ response: { sibling: RetVal("childActor") }
+ },
+ emitEvents: {
+ response: { value: "correct response" },
+ },
+ release: {
+ release: true
+ }
+ }
+});
+
+var ChildActor = protocol.ActorClassWithSpec(childSpec, {
+ // Actors returned by this actor should be owned by the root actor.
+ marshallPool: function () { return this.parent(); },
+
+ toString: function () { return "[ChildActor " + this.childID + "]"; },
+
+ initialize: function (conn, id) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ this.childID = id;
+ },
+
+ destroy: function () {
+ protocol.Actor.prototype.destroy.call(this);
+ this.destroyed = true;
+ },
+
+ form: function (detail) {
+ if (detail === "actorid") {
+ return this.actorID;
+ }
+ return {
+ actor: this.actorID,
+ childID: this.childID,
+ detail: detail
+ };
+ },
+
+ echo: function (str) {
+ return str;
+ },
+
+ getDetail1: function () {
+ return this;
+ },
+
+ getDetail2: function () {
+ return this;
+ },
+
+ getIDDetail: function () {
+ return this;
+ },
+
+ getIntArray: function (inputArray) {
+ // Test that protocol.js converts an iterator to an array.
+ let f = function* () {
+ for (let i of inputArray) {
+ yield 2 * i;
+ }
+ };
+ return f();
+ },
+
+ getSibling: function (id) {
+ return this.parent().getChild(id);
+ },
+
+ emitEvents: function () {
+ events.emit(this, "event1", 1, 2, 3);
+ events.emit(this, "event2", 4, 5, 6);
+ events.emit(this, "named-event", 1, 2, 3);
+ events.emit(this, "object-event", this);
+ events.emit(this, "array-object-event", [this]);
+ },
+
+ release: function () { },
+});
+
+var ChildFront = protocol.FrontClassWithSpec(childSpec, {
+ initialize: function (client, form) {
+ protocol.Front.prototype.initialize.call(this, client, form);
+ },
+
+ destroy: function () {
+ this.destroyed = true;
+ protocol.Front.prototype.destroy.call(this);
+ },
+
+ marshallPool: function () { return this.parent(); },
+
+ toString: function () { return "[child front " + this.childID + "]"; },
+
+ form: function (form, detail) {
+ if (detail === "actorid") {
+ return;
+ }
+ this.childID = form.childID;
+ this.detail = form.detail;
+ },
+
+ onEvent1: preEvent("event1", function (a, b, c) {
+ this.event1arg3 = c;
+ }),
+
+ onEvent2a: preEvent("event2", function (a, b, c) {
+ return promise.resolve().then(() => this.event2arg3 = c);
+ }),
+
+ onEvent2b: preEvent("event2", function (a, b, c) {
+ this.event2arg2 = b;
+ }),
+});
+
+types.addDictType("manyChildrenDict", {
+ child5: "childActor",
+ more: "array:childActor",
+});
+
+types.addLifetime("temp", "_temporaryHolder");
+
+const rootSpec = protocol.generateActorSpec({
+ typeName: "root",
+
+ methods: {
+ getChild: {
+ request: { str: Arg(0) },
+ response: { actor: RetVal("childActor") },
+ },
+ getChildren: {
+ request: { ids: Arg(0, "array:string") },
+ response: { children: RetVal("array:childActor") },
+ },
+ getChildren2: {
+ request: { ids: Arg(0, "array:childActor") },
+ response: { children: RetVal("array:childActor") },
+ },
+ getManyChildren: {
+ response: RetVal("manyChildrenDict")
+ },
+ getTemporaryChild: {
+ request: { id: Arg(0) },
+ response: { child: RetVal("temp:childActor") }
+ },
+ clearTemporaryChildren: {}
+ }
+});
+
+var rootActor = null;
+var RootActor = protocol.ActorClassWithSpec(rootSpec, {
+ toString: function () { return "[root actor]"; },
+
+ initialize: function (conn) {
+ rootActor = this;
+ this.actorID = "root";
+ this._children = {};
+ protocol.Actor.prototype.initialize.call(this, conn);
+ // Root actor owns itself.
+ this.manage(this);
+ },
+
+ sayHello: simpleHello,
+
+ getChild: function (id) {
+ if (id in this._children) {
+ return this._children[id];
+ }
+ let child = new ChildActor(this.conn, id);
+ this._children[id] = child;
+ return child;
+ },
+
+ getChildren: function (ids) {
+ return ids.map(id => this.getChild(id));
+ },
+
+ getChildren2: function (ids) {
+ let f = function* () {
+ for (let c of ids) {
+ yield c;
+ }
+ };
+ return f();
+ },
+
+ getManyChildren: function () {
+ return {
+ foo: "bar", // note that this isn't in the specialization array.
+ child5: this.getChild("child5"),
+ more: [ this.getChild("child6"), this.getChild("child7") ]
+ };
+ },
+
+ // This should remind you of a pause actor.
+ getTemporaryChild: function (id) {
+ if (!this._temporaryHolder) {
+ this._temporaryHolder = this.manage(new protocol.Actor(this.conn));
+ }
+ return new ChildActor(this.conn, id);
+ },
+
+ clearTemporaryChildren: function (id) {
+ if (!this._temporaryHolder) {
+ return;
+ }
+ this._temporaryHolder.destroy();
+ delete this._temporaryHolder;
+ }
+});
+
+var RootFront = protocol.FrontClassWithSpec(rootSpec, {
+ toString: function () { return "[root front]"; },
+ initialize: function (client) {
+ this.actorID = "root";
+ protocol.Front.prototype.initialize.call(this, client);
+ // Root actor owns itself.
+ this.manage(this);
+ },
+
+ getTemporaryChild: protocol.custom(function (id) {
+ if (!this._temporaryHolder) {
+ this._temporaryHolder = protocol.Front(this.conn);
+ this._temporaryHolder.actorID = this.actorID + "_temp";
+ this._temporaryHolder = this.manage(this._temporaryHolder);
+ }
+ return this._getTemporaryChild(id);
+ }, {
+ impl: "_getTemporaryChild"
+ }),
+
+ clearTemporaryChildren: protocol.custom(function () {
+ if (!this._temporaryHolder) {
+ return promise.resolve(undefined);
+ }
+ this._temporaryHolder.destroy();
+ delete this._temporaryHolder;
+ return this._clearTemporaryChildren();
+ }, {
+ impl: "_clearTemporaryChildren"
+ })
+});
+
+function run_test()
+{
+ DebuggerServer.createRootActor = (conn => {
+ return RootActor(conn);
+ });
+ DebuggerServer.init();
+
+ let trace = connectPipeTracing();
+ let client = new DebuggerClient(trace);
+ client.connect().then(([applicationType, traits]) => {
+ trace.expectReceive({"from":"<actorid>", "applicationType":"xpcshell-tests", "traits":[]});
+ do_check_eq(applicationType, "xpcshell-tests");
+
+ let rootFront = RootFront(client);
+ let childFront = null;
+
+ let expectRootChildren = size => {
+ do_check_eq(rootActor._poolMap.size, size + 1);
+ do_check_eq(rootFront._poolMap.size, size + 1);
+ if (childFront) {
+ do_check_eq(childFront._poolMap.size, 0);
+ }
+ };
+
+ rootFront.getChild("child1").then(ret => {
+ trace.expectSend({"type":"getChild", "str":"child1", "to":"<actorid>"});
+ trace.expectReceive({"actor":"<actorid>", "from":"<actorid>"});
+
+ childFront = ret;
+ do_check_true(childFront instanceof ChildFront);
+ do_check_eq(childFront.childID, "child1");
+ expectRootChildren(1);
+ }).then(() => {
+ // Request the child again, make sure the same is returned.
+ return rootFront.getChild("child1");
+ }).then(ret => {
+ trace.expectSend({"type":"getChild", "str":"child1", "to":"<actorid>"});
+ trace.expectReceive({"actor":"<actorid>", "from":"<actorid>"});
+
+ expectRootChildren(1);
+ do_check_true(ret === childFront);
+ }).then(() => {
+ return childFront.echo("hello");
+ }).then(ret => {
+ trace.expectSend({"type":"echo", "str":"hello", "to":"<actorid>"});
+ trace.expectReceive({"str":"hello", "from":"<actorid>"});
+
+ do_check_eq(ret, "hello");
+ }).then(() => {
+ return childFront.getDetail1();
+ }).then(ret => {
+ trace.expectSend({"type":"getDetail1", "to":"<actorid>"});
+ trace.expectReceive({"actor":"<actorid>", "childID":"child1", "detail":"detail1", "from":"<actorid>"});
+ do_check_true(ret === childFront);
+ do_check_eq(childFront.detail, "detail1");
+ }).then(() => {
+ return childFront.getDetail2();
+ }).then(ret => {
+ trace.expectSend({"type":"getDetail2", "to":"<actorid>"});
+ trace.expectReceive({"actor":"<actorid>", "childID":"child1", "detail":"detail2", "from":"<actorid>"});
+ do_check_true(ret === childFront);
+ do_check_eq(childFront.detail, "detail2");
+ }).then(() => {
+ return childFront.getIDDetail();
+ }).then(ret => {
+ trace.expectSend({"type":"getIDDetail", "to":"<actorid>"});
+ trace.expectReceive({"idDetail": childFront.actorID, "from":"<actorid>"});
+ do_check_true(ret === childFront);
+ }).then(() => {
+ return childFront.getSibling("siblingID");
+ }).then(ret => {
+ trace.expectSend({"type":"getSibling", "id":"siblingID", "to":"<actorid>"});
+ trace.expectReceive({"sibling":{"actor":"<actorid>", "childID":"siblingID"}, "from":"<actorid>"});
+
+ expectRootChildren(2);
+ }).then(ret => {
+ return rootFront.getTemporaryChild("temp1").then(temp1 => {
+ trace.expectSend({"type":"getTemporaryChild", "id":"temp1", "to":"<actorid>"});
+ trace.expectReceive({"child":{"actor":"<actorid>", "childID":"temp1"}, "from":"<actorid>"});
+
+ // At this point we expect two direct children, plus the temporary holder
+ // which should hold 1 itself.
+ do_check_eq(rootActor._temporaryHolder.__poolMap.size, 1);
+ do_check_eq(rootFront._temporaryHolder.__poolMap.size, 1);
+
+ expectRootChildren(3);
+ return rootFront.getTemporaryChild("temp2").then(temp2 => {
+ trace.expectSend({"type":"getTemporaryChild", "id":"temp2", "to":"<actorid>"});
+ trace.expectReceive({"child":{"actor":"<actorid>", "childID":"temp2"}, "from":"<actorid>"});
+
+ // Same amount of direct children, and an extra in the temporary holder.
+ expectRootChildren(3);
+ do_check_eq(rootActor._temporaryHolder.__poolMap.size, 2);
+ do_check_eq(rootFront._temporaryHolder.__poolMap.size, 2);
+
+ // Get the children of the temporary holder...
+ let checkActors = rootActor._temporaryHolder.__poolMap.values();
+ let checkFronts = rootFront._temporaryHolder.__poolMap.values();
+
+ // Now release the temporary holders and expect them to drop again.
+ return rootFront.clearTemporaryChildren().then(() => {
+ trace.expectSend({"type":"clearTemporaryChildren", "to":"<actorid>"});
+ trace.expectReceive({"from":"<actorid>"});
+
+ expectRootChildren(2);
+ do_check_false(!!rootActor._temporaryHolder);
+ do_check_false(!!rootFront._temporaryHolder);
+ for (let checkActor of checkActors) {
+ do_check_true(checkActor.destroyed);
+ do_check_true(checkActor.destroyed);
+ }
+ });
+ });
+ });
+ }).then(ret => {
+ return rootFront.getChildren(["child1", "child2"]);
+ }).then(ret => {
+ trace.expectSend({"type":"getChildren", "ids":["child1", "child2"], "to":"<actorid>"});
+ trace.expectReceive({"children":[{"actor":"<actorid>", "childID":"child1"}, {"actor":"<actorid>", "childID":"child2"}], "from":"<actorid>"});
+
+ expectRootChildren(3);
+ do_check_true(ret[0] === childFront);
+ do_check_true(ret[1] !== childFront);
+ do_check_true(ret[1] instanceof ChildFront);
+
+ // On both children, listen to events. We're only
+ // going to trigger events on the first child, so an event
+ // triggered on the second should cause immediate failures.
+
+ let set = new Set(["event1", "event2", "named-event", "object-event", "array-object-event"]);
+
+ childFront.on("event1", (a, b, c) => {
+ do_check_eq(a, 1);
+ do_check_eq(b, 2);
+ do_check_eq(c, 3);
+ // Verify that the pre-event handler was called.
+ do_check_eq(childFront.event1arg3, 3);
+ set.delete("event1");
+ });
+ childFront.on("event2", (a, b, c) => {
+ do_check_eq(a, 4);
+ do_check_eq(b, 5);
+ do_check_eq(c, 6);
+ // Verify that the async pre-event handler was called,
+ // setting the property before this handler was called.
+ do_check_eq(childFront.event2arg3, 6);
+ // And check that the sync preEvent with the same name is also
+ // executed
+ do_check_eq(childFront.event2arg2, 5);
+ set.delete("event2");
+ });
+ childFront.on("named-event", (a, b, c) => {
+ do_check_eq(a, 1);
+ do_check_eq(b, 2);
+ do_check_eq(c, 3);
+ set.delete("named-event");
+ });
+ childFront.on("object-event", (obj) => {
+ do_check_true(obj === childFront);
+ do_check_eq(childFront.detail, "detail1");
+ set.delete("object-event");
+ });
+ childFront.on("array-object-event", (array) => {
+ do_check_true(array[0] === childFront);
+ do_check_eq(childFront.detail, "detail2");
+ set.delete("array-object-event");
+ });
+
+ let fail = function () {
+ do_throw("Unexpected event");
+ };
+ ret[1].on("event1", fail);
+ ret[1].on("event2", fail);
+ ret[1].on("named-event", fail);
+ ret[1].on("object-event", fail);
+ ret[1].on("array-object-event", fail);
+
+ return childFront.emitEvents().then(() => {
+ trace.expectSend({"type":"emitEvents", "to":"<actorid>"});
+ trace.expectReceive({"type":"event1", "a":1, "b":2, "c":3, "from":"<actorid>"});
+ trace.expectReceive({"type":"event2", "a":4, "b":5, "c":6, "from":"<actorid>"});
+ trace.expectReceive({"type":"namedEvent", "a":1, "b":2, "c":3, "from":"<actorid>"});
+ trace.expectReceive({"type":"objectEvent", "detail":{"actor":"<actorid>", "childID":"child1", "detail":"detail1"}, "from":"<actorid>"});
+ trace.expectReceive({"type":"arrayObjectEvent", "detail":[{"actor":"<actorid>", "childID":"child1", "detail":"detail2"}], "from":"<actorid>"});
+ trace.expectReceive({"value":"correct response", "from":"<actorid>"});
+
+
+ do_check_eq(set.size, 0);
+ });
+ }).then(ret => {
+ return rootFront.getManyChildren();
+ }).then(ret => {
+ trace.expectSend({"type":"getManyChildren", "to":"<actorid>"});
+ trace.expectReceive({"foo":"bar", "child5":{"actor":"<actorid>", "childID":"child5"}, "more":[{"actor":"<actorid>", "childID":"child6"}, {"actor":"<actorid>", "childID":"child7"}], "from":"<actorid>"});
+
+ // Check all the crazy stuff we did in getManyChildren
+ do_check_eq(ret.foo, "bar");
+ do_check_eq(ret.child5.childID, "child5");
+ do_check_eq(ret.more[0].childID, "child6");
+ do_check_eq(ret.more[1].childID, "child7");
+ }).then(() => {
+ // Test accepting a generator.
+ let f = function* () {
+ for (let i of [1, 2, 3, 4, 5]) {
+ yield i;
+ }
+ };
+ return childFront.getIntArray(f());
+ }).then((ret) => {
+ do_check_eq(ret.length, 5);
+ let expected = [2, 4, 6, 8, 10];
+ for (let i = 0; i < 5; ++i) {
+ do_check_eq(ret[i], expected[i]);
+ }
+ }).then(() => {
+ return rootFront.getChildren(["child1", "child2"]);
+ }).then(ids => {
+ let f = function* () {
+ for (let id of ids) {
+ yield id;
+ }
+ };
+ return rootFront.getChildren2(f());
+ }).then(ret => {
+ do_check_eq(ret.length, 2);
+ do_check_true(ret[0] === childFront);
+ do_check_true(ret[1] !== childFront);
+ do_check_true(ret[1] instanceof ChildFront);
+ }).then(() => {
+ client.close().then(() => {
+ do_test_finished();
+ });
+ }).then(null, err => {
+ do_report_unexpected_exception(err, "Failure executing test");
+ });
+ });
+ do_test_pending();
+}
diff --git a/devtools/server/tests/unit/test_protocol_formtype.js b/devtools/server/tests/unit/test_protocol_formtype.js
new file mode 100644
index 000000000..27ac0bee9
--- /dev/null
+++ b/devtools/server/tests/unit/test_protocol_formtype.js
@@ -0,0 +1,177 @@
+var protocol = require("devtools/shared/protocol");
+var {Arg, Option, RetVal} = protocol;
+
+protocol.types.addActorType("child");
+protocol.types.addActorType("root");
+
+const childSpec = protocol.generateActorSpec({
+ typeName: "child",
+
+ methods: {
+ getChild: {
+ response: RetVal("child")
+ }
+ }
+});
+
+// The child actor doesn't provide a form description
+var ChildActor = protocol.ActorClassWithSpec(childSpec, {
+ initialize(conn) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ },
+
+ form(detail) {
+ return {
+ actor: this.actorID,
+ extra: "extra"
+ };
+ },
+
+ getChild: function () {
+ return this;
+ }
+});
+
+var ChildFront = protocol.FrontClassWithSpec(childSpec, {
+ initialize(client) {
+ protocol.Front.prototype.initialize.call(this, client);
+ },
+
+ form(v, ctx, detail) {
+ this.extra = v.extra;
+ }
+});
+
+const rootSpec = protocol.generateActorSpec({
+ typeName: "root",
+
+ // Basic form type, relies on implicit DictType creation
+ formType: {
+ childActor: "child"
+ },
+
+ // This detail uses explicit DictType creation
+ "formType#detail1": protocol.types.addDictType("RootActorFormTypeDetail1", {
+ detailItem: "child"
+ }),
+
+ // This detail a string type.
+ "formType#actorid": "string",
+
+ methods: {
+ getDefault: {
+ response: RetVal("root")
+ },
+ getDetail1: {
+ response: RetVal("root#detail1")
+ },
+ getDetail2: {
+ response: {
+ item: RetVal("root#actorid")
+ }
+ },
+ getUnknownDetail: {
+ response: RetVal("root#unknownDetail")
+ }
+ }
+});
+
+// The root actor does provide a form description.
+var RootActor = protocol.ActorClassWithSpec(rootSpec, {
+ initialize(conn) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ this.manage(this);
+ this.child = new ChildActor();
+ },
+
+ sayHello() {
+ return {
+ from: "root",
+ applicationType: "xpcshell-tests",
+ traits: []
+ };
+ },
+
+ form(detail) {
+ if (detail === "detail1") {
+ return {
+ actor: this.actorID,
+ detailItem: this.child
+ };
+ } else if (detail === "actorid") {
+ return this.actorID;
+ }
+
+ return {
+ actor: this.actorID,
+ childActor: this.child
+ };
+ },
+
+ getDefault: function () {
+ return this;
+ },
+
+ getDetail1: function () {
+ return this;
+ },
+
+ getDetail2: function () {
+ return this;
+ },
+
+ getUnknownDetail: function () {
+ return this;
+ }
+});
+
+var RootFront = protocol.FrontClassWithSpec(rootSpec, {
+ initialize(client) {
+ this.actorID = "root";
+ protocol.Front.prototype.initialize.call(this, client);
+
+ // Root owns itself.
+ this.manage(this);
+ },
+
+ form(v, ctx, detail) {
+ this.lastForm = v;
+ }
+});
+
+const run_test = Test(function* () {
+ DebuggerServer.createRootActor = (conn => {
+ return RootActor(conn);
+ });
+ DebuggerServer.init();
+
+ const connection = DebuggerServer.connectPipe();
+ const conn = new DebuggerClient(connection);
+ const client = Async(conn);
+
+ yield client.connect();
+
+ let rootFront = RootFront(conn);
+
+ // Trigger some methods that return forms.
+ let retval = yield rootFront.getDefault();
+ do_check_true(retval instanceof RootFront);
+ do_check_true(rootFront.lastForm.childActor instanceof ChildFront);
+
+ retval = yield rootFront.getDetail1();
+ do_check_true(retval instanceof RootFront);
+ do_check_true(rootFront.lastForm.detailItem instanceof ChildFront);
+
+ retval = yield rootFront.getDetail2();
+ do_check_true(retval instanceof RootFront);
+ do_check_true(typeof (rootFront.lastForm) === "string");
+
+ // getUnknownDetail should fail, since no typeName is specified.
+ try {
+ yield rootFront.getUnknownDetail();
+ do_check_true(false);
+ } catch (ex) {
+ }
+
+ yield client.close();
+});
diff --git a/devtools/server/tests/unit/test_protocol_longstring.js b/devtools/server/tests/unit/test_protocol_longstring.js
new file mode 100644
index 000000000..c37f4251e
--- /dev/null
+++ b/devtools/server/tests/unit/test_protocol_longstring.js
@@ -0,0 +1,218 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test simple requests using the protocol helpers.
+ */
+var protocol = require("devtools/shared/protocol");
+var {RetVal, Arg, Option} = protocol;
+var events = require("sdk/event/core");
+var {LongStringActor} = require("devtools/server/actors/string");
+
+// The test implicitly relies on this.
+require("devtools/shared/fronts/string");
+
+function simpleHello() {
+ return {
+ from: "root",
+ applicationType: "xpcshell-tests",
+ traits: [],
+ };
+}
+
+DebuggerServer.LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_INITIAL_LENGTH = DebuggerServer.LONG_STRING_READ_LENGTH = 5;
+
+var SHORT_STR = "abc";
+var LONG_STR = "abcdefghijklmnop";
+
+var rootActor = null;
+
+const rootSpec = protocol.generateActorSpec({
+ typeName: "root",
+
+ events: {
+ "string-event": {
+ str: Arg(0, "longstring")
+ }
+ },
+
+ methods: {
+ shortString: {
+ response: { value: RetVal("longstring") },
+ },
+ longString: {
+ response: { value: RetVal("longstring") },
+ },
+ emitShortString: {
+ oneway: true,
+ },
+ emitLongString: {
+ oneway: true,
+ }
+ }
+});
+
+var RootActor = protocol.ActorClassWithSpec(rootSpec, {
+ initialize: function (conn) {
+ rootActor = this;
+ protocol.Actor.prototype.initialize.call(this, conn);
+ // Root actor owns itself.
+ this.manage(this);
+ this.actorID = "root";
+ },
+
+ sayHello: simpleHello,
+
+ shortString: function () {
+ return new LongStringActor(this.conn, SHORT_STR);
+ },
+
+ longString: function () {
+ return new LongStringActor(this.conn, LONG_STR);
+ },
+
+ emitShortString: function () {
+ events.emit(this, "string-event", new LongStringActor(this.conn, SHORT_STR));
+ },
+
+ emitLongString: function () {
+ events.emit(this, "string-event", new LongStringActor(this.conn, LONG_STR));
+ },
+});
+
+var RootFront = protocol.FrontClassWithSpec(rootSpec, {
+ initialize: function (client) {
+ this.actorID = "root";
+ protocol.Front.prototype.initialize.call(this, client);
+ // Root owns itself.
+ this.manage(this);
+ }
+});
+
+function run_test()
+{
+ DebuggerServer.createRootActor = (conn => {
+ return RootActor(conn);
+ });
+
+ DebuggerServer.init();
+ let trace = connectPipeTracing();
+ let client = new DebuggerClient(trace);
+ let rootClient;
+
+ let strfront = null;
+
+ let expectRootChildren = function (size) {
+ do_check_eq(rootActor.__poolMap.size, size + 1);
+ do_check_eq(rootClient.__poolMap.size, size + 1);
+ };
+
+
+ client.connect().then(([applicationType, traits]) => {
+ rootClient = RootFront(client);
+
+ // Root actor has no children yet.
+ expectRootChildren(0);
+
+ trace.expectReceive({"from":"<actorid>", "applicationType":"xpcshell-tests", "traits":[]});
+ do_check_eq(applicationType, "xpcshell-tests");
+ rootClient.shortString().then(ret => {
+ trace.expectSend({"type":"shortString", "to":"<actorid>"});
+ trace.expectReceive({"value":"abc", "from":"<actorid>"});
+
+ // Should only own the one reference (itself) at this point.
+ expectRootChildren(0);
+ strfront = ret;
+ }).then(() => {
+ return strfront.string();
+ }).then(ret => {
+ do_check_eq(ret, SHORT_STR);
+ }).then(() => {
+ return rootClient.longString();
+ }).then(ret => {
+ trace.expectSend({"type":"longString", "to":"<actorid>"});
+ trace.expectReceive({"value":{"type":"longString", "actor":"<actorid>", "length":16, "initial":"abcde"}, "from":"<actorid>"});
+
+ strfront = ret;
+ // Should own a reference to itself and an extra string now.
+ expectRootChildren(1);
+ }).then(() => {
+ return strfront.string();
+ }).then(ret => {
+ trace.expectSend({"type":"substring", "start":5, "end":10, "to":"<actorid>"});
+ trace.expectReceive({"substring":"fghij", "from":"<actorid>"});
+ trace.expectSend({"type":"substring", "start":10, "end":15, "to":"<actorid>"});
+ trace.expectReceive({"substring":"klmno", "from":"<actorid>"});
+ trace.expectSend({"type":"substring", "start":15, "end":20, "to":"<actorid>"});
+ trace.expectReceive({"substring":"p", "from":"<actorid>"});
+
+ do_check_eq(ret, LONG_STR);
+ }).then(() => {
+ return strfront.release();
+ }).then(() => {
+ trace.expectSend({"type":"release", "to":"<actorid>"});
+ trace.expectReceive({"from":"<actorid>"});
+
+ // That reference should be removed now.
+ expectRootChildren(0);
+ }).then(() => {
+ let deferred = promise.defer();
+ rootClient.once("string-event", (str) => {
+ trace.expectSend({"type":"emitShortString", "to":"<actorid>"});
+ trace.expectReceive({"type":"string-event", "str":"abc", "from":"<actorid>"});
+
+ do_check_true(!!str);
+ strfront = str;
+ // Shouldn't generate any new references
+ expectRootChildren(0);
+ // will generate no packets.
+ strfront.string().then((value) => { deferred.resolve(value); });
+ });
+ rootClient.emitShortString();
+ return deferred.promise;
+ }).then(value => {
+ do_check_eq(value, SHORT_STR);
+ }).then(() => {
+ // Will generate no packets
+ return strfront.release();
+ }).then(() => {
+ let deferred = promise.defer();
+ rootClient.once("string-event", (str) => {
+ trace.expectSend({"type":"emitLongString", "to":"<actorid>"});
+ trace.expectReceive({"type":"string-event", "str":{"type":"longString", "actor":"<actorid>", "length":16, "initial":"abcde"}, "from":"<actorid>"});
+
+ do_check_true(!!str);
+ // Should generate one new reference
+ expectRootChildren(1);
+ strfront = str;
+ strfront.string().then((value) => {
+ trace.expectSend({"type":"substring", "start":5, "end":10, "to":"<actorid>"});
+ trace.expectReceive({"substring":"fghij", "from":"<actorid>"});
+ trace.expectSend({"type":"substring", "start":10, "end":15, "to":"<actorid>"});
+ trace.expectReceive({"substring":"klmno", "from":"<actorid>"});
+ trace.expectSend({"type":"substring", "start":15, "end":20, "to":"<actorid>"});
+ trace.expectReceive({"substring":"p", "from":"<actorid>"});
+
+ deferred.resolve(value);
+ });
+ });
+ rootClient.emitLongString();
+ return deferred.promise;
+ }).then(value => {
+ do_check_eq(value, LONG_STR);
+ }).then(() => {
+ return strfront.release();
+ }).then(() => {
+ trace.expectSend({"type":"release", "to":"<actorid>"});
+ trace.expectReceive({"from":"<actorid>"});
+ expectRootChildren(0);
+ }).then(() => {
+ client.close().then(() => {
+ do_test_finished();
+ });
+ }).then(null, err => {
+ do_report_unexpected_exception(err, "Failure executing test");
+ });
+ });
+ do_test_pending();
+}
diff --git a/devtools/server/tests/unit/test_protocol_simple.js b/devtools/server/tests/unit/test_protocol_simple.js
new file mode 100644
index 000000000..c85003954
--- /dev/null
+++ b/devtools/server/tests/unit/test_protocol_simple.js
@@ -0,0 +1,319 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test simple requests using the protocol helpers.
+ */
+
+var protocol = require("devtools/shared/protocol");
+var {Arg, Option, RetVal} = protocol;
+var events = require("sdk/event/core");
+
+function simpleHello() {
+ return {
+ from: "root",
+ applicationType: "xpcshell-tests",
+ traits: [],
+ };
+}
+
+const rootSpec = protocol.generateActorSpec({
+ typeName: "root",
+
+ events: {
+ "oneway": { a: Arg(0) },
+ "falsyOptions": {
+ zero: Option(0),
+ farce: Option(0)
+ }
+ },
+
+ methods: {
+ simpleReturn: {
+ response: { value: RetVal() },
+ },
+ promiseReturn: {
+ response: { value: RetVal("number") },
+ },
+ simpleArgs: {
+ request: {
+ firstArg: Arg(0),
+ secondArg: Arg(1),
+ },
+ response: RetVal()
+ },
+ nestedArgs: {
+ request: {
+ firstArg: Arg(0),
+ nest: {
+ secondArg: Arg(1),
+ nest: {
+ thirdArg: Arg(2)
+ }
+ }
+ },
+ response: RetVal()
+ },
+ optionArgs: {
+ request: {
+ option1: Option(0),
+ option2: Option(0)
+ },
+ response: RetVal()
+ },
+ optionalArgs: {
+ request: {
+ a: Arg(0),
+ b: Arg(1, "nullable:number")
+ },
+ response: {
+ value: RetVal("number")
+ },
+ },
+ arrayArgs: {
+ request: {
+ a: Arg(0, "array:number")
+ },
+ response: {
+ arrayReturn: RetVal("array:number")
+ },
+ },
+ nestedArrayArgs: {
+ request: { a: Arg(0, "array:array:number") },
+ response: { value: RetVal("array:array:number") },
+ },
+ renamedEcho: {
+ request: {
+ type: "echo",
+ a: Arg(0),
+ },
+ response: {
+ value: RetVal("string")
+ },
+ },
+ testOneWay: {
+ request: { a: Arg(0) },
+ oneway: true
+ },
+ emitFalsyOptions: {
+ oneway: true
+ }
+ }
+});
+
+var RootActor = protocol.ActorClassWithSpec(rootSpec, {
+ initialize: function (conn) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ // Root actor owns itself.
+ this.manage(this);
+ this.actorID = "root";
+ },
+
+ sayHello: simpleHello,
+
+ simpleReturn: function () {
+ return 1;
+ },
+
+ promiseReturn: function () {
+ return promise.resolve(1);
+ },
+
+ simpleArgs: function (a, b) {
+ return { firstResponse: a + 1, secondResponse: b + 1 };
+ },
+
+ nestedArgs: function (a, b, c) {
+ return { a: a, b: b, c: c };
+ },
+
+ optionArgs: function (options) {
+ return { option1: options.option1, option2: options.option2 };
+ },
+
+ optionalArgs: function (a, b = 200) {
+ return b;
+ },
+
+ arrayArgs: function (a) {
+ return a;
+ },
+
+ nestedArrayArgs: function (a) {
+ return a;
+ },
+
+ /**
+ * Test that the 'type' part of the request packet works
+ * correctly when the type isn't the same as the method name
+ */
+ renamedEcho: function (a) {
+ if (this.conn.currentPacket.type != "echo") {
+ return "goodbye";
+ }
+ return a;
+ },
+
+ testOneWay: function (a) {
+ // Emit to show that we got this message, because there won't be a response.
+ events.emit(this, "oneway", a);
+ },
+
+ emitFalsyOptions: function () {
+ events.emit(this, "falsyOptions", { zero: 0, farce: false });
+ }
+});
+
+var RootFront = protocol.FrontClassWithSpec(rootSpec, {
+ initialize: function (client) {
+ this.actorID = "root";
+ protocol.Front.prototype.initialize.call(this, client);
+ // Root owns itself.
+ this.manage(this);
+ }
+});
+
+function run_test()
+{
+ DebuggerServer.createRootActor = (conn => {
+ return RootActor(conn);
+ });
+ DebuggerServer.init();
+
+ check_except(() => {
+ let badActor = ActorClassWithSpec({}, {
+ missing: preEvent("missing-event", function () {
+ })
+ });
+ });
+
+ protocol.types.getType("array:array:array:number");
+ protocol.types.getType("array:array:array:number");
+
+ check_except(() => protocol.types.getType("unknown"));
+ check_except(() => protocol.types.getType("array:unknown"));
+ check_except(() => protocol.types.getType("unknown:number"));
+ let trace = connectPipeTracing();
+ let client = new DebuggerClient(trace);
+ let rootClient;
+
+ client.connect().then(([applicationType, traits]) => {
+ trace.expectReceive({"from":"<actorid>", "applicationType":"xpcshell-tests", "traits":[]});
+ do_check_eq(applicationType, "xpcshell-tests");
+
+ rootClient = RootFront(client);
+
+ rootClient.simpleReturn().then(ret => {
+ trace.expectSend({"type":"simpleReturn", "to":"<actorid>"});
+ trace.expectReceive({"value":1, "from":"<actorid>"});
+ do_check_eq(ret, 1);
+ }).then(() => {
+ return rootClient.promiseReturn();
+ }).then(ret => {
+ trace.expectSend({"type":"promiseReturn", "to":"<actorid>"});
+ trace.expectReceive({"value":1, "from":"<actorid>"});
+ do_check_eq(ret, 1);
+ }).then(() => {
+ // Missing argument should throw an exception
+ check_except(() => {
+ rootClient.simpleArgs(5);
+ });
+
+ return rootClient.simpleArgs(5, 10);
+ }).then(ret => {
+ trace.expectSend({"type":"simpleArgs", "firstArg":5, "secondArg":10, "to":"<actorid>"});
+ trace.expectReceive({"firstResponse":6, "secondResponse":11, "from":"<actorid>"});
+ do_check_eq(ret.firstResponse, 6);
+ do_check_eq(ret.secondResponse, 11);
+ }).then(() => {
+ return rootClient.nestedArgs(1, 2, 3);
+ }).then(ret => {
+ trace.expectSend({"type":"nestedArgs", "firstArg":1, "nest":{"secondArg":2, "nest":{"thirdArg":3}}, "to":"<actorid>"});
+ trace.expectReceive({"a":1, "b":2, "c":3, "from":"<actorid>"});
+ do_check_eq(ret.a, 1);
+ do_check_eq(ret.b, 2);
+ do_check_eq(ret.c, 3);
+ }).then(() => {
+ return rootClient.optionArgs({
+ "option1": 5,
+ "option2": 10
+ });
+ }).then(ret => {
+ trace.expectSend({"type":"optionArgs", "option1":5, "option2":10, "to":"<actorid>"});
+ trace.expectReceive({"option1":5, "option2":10, "from":"<actorid>"});
+ do_check_eq(ret.option1, 5);
+ do_check_eq(ret.option2, 10);
+ }).then(() => {
+ return rootClient.optionArgs({});
+ }).then(ret => {
+ trace.expectSend({"type":"optionArgs", "to":"<actorid>"});
+ trace.expectReceive({"from":"<actorid>"});
+ do_check_true(typeof (ret.option1) === "undefined");
+ do_check_true(typeof (ret.option2) === "undefined");
+ }).then(() => {
+ // Explicitly call an optional argument...
+ return rootClient.optionalArgs(5, 10);
+ }).then(ret => {
+ trace.expectSend({"type":"optionalArgs", "a":5, "b":10, "to":"<actorid>"});
+ trace.expectReceive({"value":10, "from":"<actorid>"});
+ do_check_eq(ret, 10);
+ }).then(() => {
+ // Now don't pass the optional argument, expect the default.
+ return rootClient.optionalArgs(5);
+ }).then(ret => {
+ trace.expectSend({"type":"optionalArgs", "a":5, "to":"<actorid>"});
+ trace.expectReceive({"value":200, "from":"<actorid>"});
+ do_check_eq(ret, 200);
+ }).then(ret => {
+ return rootClient.arrayArgs([0, 1, 2, 3, 4, 5]);
+ }).then(ret => {
+ trace.expectSend({"type":"arrayArgs", "a":[0, 1, 2, 3, 4, 5], "to":"<actorid>"});
+ trace.expectReceive({"arrayReturn":[0, 1, 2, 3, 4, 5], "from":"<actorid>"});
+ do_check_eq(ret[0], 0);
+ do_check_eq(ret[5], 5);
+ }).then(() => {
+ return rootClient.arrayArgs([[5]]);
+ }).then(ret => {
+ trace.expectSend({"type":"arrayArgs", "a":[[5]], "to":"<actorid>"});
+ trace.expectReceive({"arrayReturn":[[5]], "from":"<actorid>"});
+ do_check_eq(ret[0][0], 5);
+ }).then(() => {
+ return rootClient.renamedEcho("hello");
+ }).then(str => {
+ trace.expectSend({"type":"echo", "a":"hello", "to":"<actorid>"});
+ trace.expectReceive({"value":"hello", "from":"<actorid>"});
+
+ do_check_eq(str, "hello");
+
+ let deferred = promise.defer();
+ rootClient.on("oneway", (response) => {
+ trace.expectSend({"type":"testOneWay", "a":"hello", "to":"<actorid>"});
+ trace.expectReceive({"type":"oneway", "a":"hello", "from":"<actorid>"});
+
+ do_check_eq(response, "hello");
+ deferred.resolve();
+ });
+ do_check_true(typeof (rootClient.testOneWay("hello")) === "undefined");
+ return deferred.promise;
+ }).then(() => {
+ let deferred = promise.defer();
+ rootClient.on("falsyOptions", res => {
+ trace.expectSend({"type":"emitFalsyOptions", "to":"<actorid>"});
+ trace.expectReceive({"type":"falsyOptions", "farce":false, "zero": 0, "from":"<actorid>"});
+
+ do_check_true(res.zero === 0);
+ do_check_true(res.farce === false);
+ deferred.resolve();
+ });
+ rootClient.emitFalsyOptions();
+ return deferred.promise;
+ }).then(() => {
+ client.close().then(() => {
+ do_test_finished();
+ });
+ }).then(null, err => {
+ do_report_unexpected_exception(err, "Failure executing test");
+ });
+ });
+ do_test_pending();
+}
diff --git a/devtools/server/tests/unit/test_protocol_stack.js b/devtools/server/tests/unit/test_protocol_stack.js
new file mode 100644
index 000000000..a81f99a8e
--- /dev/null
+++ b/devtools/server/tests/unit/test_protocol_stack.js
@@ -0,0 +1,98 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Client request stacks should span the entire process from before making the
+ * request to handling the reply from the server. The server frames are not
+ * included, nor can they be in most cases, since the server can be a remote
+ * device.
+ */
+
+var protocol = require("devtools/shared/protocol");
+var {Arg, Option, RetVal} = protocol;
+var events = require("sdk/event/core");
+
+function simpleHello() {
+ return {
+ from: "root",
+ applicationType: "xpcshell-tests",
+ traits: [],
+ };
+}
+
+const rootSpec = protocol.generateActorSpec({
+ typeName: "root",
+
+ methods: {
+ simpleReturn: {
+ response: { value: RetVal() },
+ }
+ }
+});
+
+var RootActor = protocol.ActorClassWithSpec(rootSpec, {
+ initialize: function (conn) {
+ protocol.Actor.prototype.initialize.call(this, conn);
+ // Root actor owns itself.
+ this.manage(this);
+ this.actorID = "root";
+ this.sequence = 0;
+ },
+
+ sayHello: simpleHello,
+
+ simpleReturn: function () {
+ return this.sequence++;
+ }
+});
+
+var RootFront = protocol.FrontClassWithSpec(rootSpec, {
+ initialize: function (client) {
+ this.actorID = "root";
+ protocol.Front.prototype.initialize.call(this, client);
+ // Root owns itself.
+ this.manage(this);
+ }
+});
+
+function run_test() {
+ if (!Services.prefs.getBoolPref("javascript.options.asyncstack")) {
+ do_print("Async stacks are disabled.");
+ return;
+ }
+
+ DebuggerServer.createRootActor = RootActor;
+ DebuggerServer.init();
+
+ let trace = connectPipeTracing();
+ let client = new DebuggerClient(trace);
+ let rootClient;
+
+ client.connect().then(function onConnect() {
+ rootClient = RootFront(client);
+
+ rootClient.simpleReturn().then(() => {
+ let stack = Components.stack;
+ while (stack) {
+ do_print(stack.name);
+ if (stack.name == "onConnect") {
+ // Reached back to outer function before request
+ ok(true, "Complete stack");
+ return;
+ }
+ stack = stack.asyncCaller || stack.caller;
+ }
+ ok(false, "Incomplete stack");
+ }, () => {
+ ok(false, "Request failed unexpectedly");
+ }).then(() => {
+ client.close().then(() => {
+ do_test_finished();
+ });
+ });
+ });
+
+ do_test_pending();
+}
diff --git a/devtools/server/tests/unit/test_protocol_unregister.js b/devtools/server/tests/unit/test_protocol_unregister.js
new file mode 100644
index 000000000..5b32dd0a3
--- /dev/null
+++ b/devtools/server/tests/unit/test_protocol_unregister.js
@@ -0,0 +1,44 @@
+const {types} = require("devtools/shared/protocol");
+
+
+function run_test()
+{
+ types.addType("test", {
+ read: (v) => { return "successful read: " + v; },
+ write: (v) => { return "successful write: " + v; }
+ });
+
+ // Verify the type registered correctly.
+
+ let type = types.getType("test");
+ let arrayType = types.getType("array:test");
+ do_check_eq(type.read("foo"), "successful read: foo");
+ do_check_eq(arrayType.read(["foo"])[0], "successful read: foo");
+
+ types.removeType("test");
+
+ do_check_eq(type.name, "DEFUNCT:test");
+ try {
+ types.getType("test");
+ do_check_true(false, "getType should fail");
+ } catch (ex) {
+ do_check_eq(ex.toString(), "Error: Unknown type: test");
+ }
+
+ try {
+ type.read("foo");
+ do_check_true(false, "type.read should have thrown an exception.");
+ } catch (ex) {
+ do_check_eq(ex.toString(), "Error: Using defunct type: test");
+ }
+
+ try {
+ arrayType.read(["foo"]);
+ do_check_true(false, "array:test.read should have thrown an exception.");
+ } catch (ex) {
+ do_check_eq(ex.toString(), "Error: Using defunct type: test");
+ }
+
+}
+
+
diff --git a/devtools/server/tests/unit/test_reattach-thread.js b/devtools/server/tests/unit/test_reattach-thread.js
new file mode 100644
index 000000000..6d089e896
--- /dev/null
+++ b/devtools/server/tests/unit/test_reattach-thread.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that reattaching to a previously detached thread works.
+ */
+
+var gClient, gDebuggee, gThreadClient, gTabClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = testGlobal("test-reattach");
+ DebuggerServer.addTestGlobal(gDebuggee);
+
+ let transport = DebuggerServer.connectPipe();
+ gClient = new DebuggerClient(transport);
+ gClient.connect().then(() => {
+ attachTestTab(gClient, "test-reattach", (aReply, aTabClient) => {
+ gTabClient = aTabClient;
+ test_attach();
+ });
+ });
+ do_test_pending();
+}
+
+function test_attach()
+{
+ gTabClient.attachThread({}, (aResponse, aThreadClient) => {
+ do_check_eq(aThreadClient.state, "paused");
+ gThreadClient = aThreadClient;
+ aThreadClient.resume(test_detach);
+ });
+}
+
+function test_detach()
+{
+ gThreadClient.detach(() => {
+ do_check_eq(gThreadClient.state, "detached");
+ do_check_eq(gTabClient.thread, null);
+ test_reattach();
+ });
+}
+
+function test_reattach()
+{
+ gTabClient.attachThread({}, (aResponse, aThreadClient) => {
+ do_check_neq(gThreadClient, aThreadClient);
+ do_check_eq(aThreadClient.state, "paused");
+ do_check_eq(gTabClient.thread, aThreadClient);
+ aThreadClient.resume(cleanup);
+ });
+}
+
+function cleanup()
+{
+ gClient.close().then(do_test_finished);
+}
diff --git a/devtools/server/tests/unit/test_registerClient.js b/devtools/server/tests/unit/test_registerClient.js
new file mode 100644
index 000000000..c018e4454
--- /dev/null
+++ b/devtools/server/tests/unit/test_registerClient.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the DebuggerClient.registerClient API
+
+var EventEmitter = require("devtools/shared/event-emitter");
+
+var gClient;
+var gActors;
+var gTestClient;
+
+function TestActor(conn) {
+ this.conn = conn;
+}
+TestActor.prototype = {
+ actorPrefix: "test",
+
+ start: function () {
+ this.conn.sendActorEvent(this.actorID, "foo", {
+ hello: "world"
+ });
+ return {};
+ }
+};
+TestActor.prototype.requestTypes = {
+ "start": TestActor.prototype.start
+};
+
+function TestClient(client, form) {
+ this.client = client;
+ this.actor = form.test;
+ this.events = ["foo"];
+ EventEmitter.decorate(this);
+ client.registerClient(this);
+
+ this.detached = false;
+}
+TestClient.prototype = {
+ start: function () {
+ this.client.request({
+ to: this.actor,
+ type: "start"
+ });
+ },
+
+ detach: function (onDone) {
+ this.detached = true;
+ onDone();
+ }
+};
+
+function run_test()
+{
+ DebuggerServer.addGlobalActor(TestActor);
+
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ add_test(init);
+ add_test(test_client_events);
+ add_test(close_client);
+ run_next_test();
+}
+
+function init()
+{
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect()
+ .then(() => gClient.listTabs())
+ .then(aResponse => {
+ gActors = aResponse;
+ gTestClient = new TestClient(gClient, aResponse);
+ run_next_test();
+ });
+}
+
+function test_client_events()
+{
+ // Test DebuggerClient.registerClient and DebuggerServerConnection.sendActorEvent
+ gTestClient.on("foo", function (type, data) {
+ do_check_eq(type, "foo");
+ do_check_eq(data.hello, "world");
+ run_next_test();
+ });
+ gTestClient.start();
+}
+
+function close_client() {
+ gClient.close().then(() => {
+ // Check that client.detach method is call on client destruction
+ do_check_true(gTestClient.detached);
+ run_next_test();
+ });
+}
+
diff --git a/devtools/server/tests/unit/test_register_actor.js b/devtools/server/tests/unit/test_register_actor.js
new file mode 100644
index 000000000..8f3a243eb
--- /dev/null
+++ b/devtools/server/tests/unit/test_register_actor.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+
+function check_actors(expect) {
+ do_check_eq(expect, DebuggerServer.tabActorFactories.hasOwnProperty("registeredActor1"));
+ do_check_eq(expect, DebuggerServer.tabActorFactories.hasOwnProperty("registeredActor2"));
+
+ do_check_eq(expect, DebuggerServer.globalActorFactories.hasOwnProperty("registeredActor2"));
+ do_check_eq(expect, DebuggerServer.globalActorFactories.hasOwnProperty("registeredActor1"));
+}
+
+function run_test()
+{
+ // Allow incoming connections.
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ add_test(test_deprecated_api);
+ add_test(test_lazy_api);
+ add_test(cleanup);
+ run_next_test();
+}
+
+function test_deprecated_api() {
+ // The xpcshell-test/ path maps to resource://test/
+ DebuggerServer.registerModule("xpcshell-test/registertestactors-01");
+ DebuggerServer.registerModule("xpcshell-test/registertestactors-02");
+
+ check_actors(true);
+
+ check_except(() => {
+ DebuggerServer.registerModule("xpcshell-test/registertestactors-01");
+ });
+ check_except(() => {
+ DebuggerServer.registerModule("xpcshell-test/registertestactors-02");
+ });
+
+ DebuggerServer.unregisterModule("xpcshell-test/registertestactors-01");
+ DebuggerServer.unregisterModule("xpcshell-test/registertestactors-02");
+ check_actors(false);
+
+ DebuggerServer.registerModule("xpcshell-test/registertestactors-01");
+ DebuggerServer.registerModule("xpcshell-test/registertestactors-02");
+ check_actors(true);
+
+ run_next_test();
+}
+
+// Bug 988237: Test the new lazy actor loading
+function test_lazy_api() {
+ let isActorLoaded = false;
+ let isActorInstanciated = false;
+ function onActorEvent(subject, topic, data) {
+ if (data == "loaded") {
+ isActorLoaded = true;
+ } else if (data == "instantiated") {
+ isActorInstanciated = true;
+ }
+ }
+ Services.obs.addObserver(onActorEvent, "actor", false);
+ DebuggerServer.registerModule("xpcshell-test/registertestactors-03", {
+ prefix: "lazy",
+ constructor: "LazyActor",
+ type: { global: true, tab: true }
+ });
+ // The actor is immediatly registered, but not loaded
+ do_check_true(DebuggerServer.tabActorFactories.hasOwnProperty("lazyActor"));
+ do_check_true(DebuggerServer.globalActorFactories.hasOwnProperty("lazyActor"));
+ do_check_false(isActorLoaded);
+ do_check_false(isActorInstanciated);
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(onListTabs);
+ });
+ function onListTabs(aResponse) {
+ // On listTabs, the actor is still not loaded,
+ // but we can see its name in the list of available actors
+ do_check_false(isActorLoaded);
+ do_check_false(isActorInstanciated);
+ do_check_true("lazyActor" in aResponse);
+
+ let {LazyFront} = require("xpcshell-test/registertestactors-03");
+ let front = LazyFront(client, aResponse);
+ front.hello().then(onRequest);
+ }
+ function onRequest(aResponse) {
+ do_check_eq(aResponse, "world");
+
+ // Finally, the actor is loaded on the first request being made to it
+ do_check_true(isActorLoaded);
+ do_check_true(isActorInstanciated);
+
+ Services.obs.removeObserver(onActorEvent, "actor", false);
+ client.close().then(() => run_next_test());
+ }
+}
+
+function cleanup() {
+ DebuggerServer.destroy();
+
+ // Check that all actors are unregistered on server destruction
+ check_actors(false);
+ do_check_false(DebuggerServer.tabActorFactories.hasOwnProperty("lazyActor"));
+ do_check_false(DebuggerServer.globalActorFactories.hasOwnProperty("lazyActor"));
+
+ run_next_test();
+}
+
diff --git a/devtools/server/tests/unit/test_requestTypes.js b/devtools/server/tests/unit/test_requestTypes.js
new file mode 100644
index 000000000..694e276bc
--- /dev/null
+++ b/devtools/server/tests/unit/test_requestTypes.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { RootActor } = require("devtools/server/actors/root");
+
+function test_requestTypes_request(aClient, anActor)
+{
+ aClient.request({ to: "root", type: "requestTypes" }, function (aResponse) {
+ var expectedRequestTypes = Object.keys(RootActor.
+ prototype.
+ requestTypes);
+
+ do_check_true(Array.isArray(aResponse.requestTypes));
+ do_check_eq(JSON.stringify(aResponse.requestTypes),
+ JSON.stringify(expectedRequestTypes));
+
+ aClient.close().then(() => {
+ do_test_finished();
+ });
+ });
+}
+
+function run_test()
+{
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function () {
+ test_requestTypes_request(client);
+ });
+
+ do_test_pending();
+}
diff --git a/devtools/server/tests/unit/test_safe-getter.js b/devtools/server/tests/unit/test_safe-getter.js
new file mode 100644
index 000000000..08949154d
--- /dev/null
+++ b/devtools/server/tests/unit/test_safe-getter.js
@@ -0,0 +1,25 @@
+function run_test() {
+ Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+ addDebuggerToGlobal(this);
+ var g = testGlobal("test");
+ var dbg = new Debugger();
+ var gw = dbg.addDebuggee(g);
+
+ g.eval(`
+ // This is not a CCW.
+ Object.defineProperty(this, "bar", {
+ get: function() { return "bar"; },
+ configurable: true,
+ enumerable: true
+ });
+
+ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ // This is a CCW.
+ XPCOMUtils.defineLazyGetter(this, "foo", function() { return "foo"; });
+ `);
+
+ // Neither scripted getter should be considered safe.
+ assert(!DevToolsUtils.hasSafeGetter(gw.getOwnPropertyDescriptor("bar")));
+ assert(!DevToolsUtils.hasSafeGetter(gw.getOwnPropertyDescriptor("foo")));
+}
diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js b/devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js
new file mode 100644
index 000000000..a9d82e434
--- /dev/null
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-column-in-gcd-script.js
@@ -0,0 +1,58 @@
+"use strict";
+
+var SOURCE_URL = getFileUrl("setBreakpoint-on-column-in-gcd-script.js");
+
+function run_test() {
+ return Task.spawn(function* () {
+ do_test_pending();
+
+ let global = testGlobal("test");
+ loadSubScript(SOURCE_URL, global);
+ Cu.forceGC(); Cu.forceGC(); Cu.forceGC();
+
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+ DebuggerServer.init(() => true);
+ DebuggerServer.addTestGlobal(global);
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let { tabs } = yield listTabs(client);
+ let tab = findTab(tabs, "test");
+ let [, tabClient] = yield attachTab(client, tab);
+ let [, threadClient] = yield attachThread(tabClient);
+ yield resume(threadClient);
+
+ let { sources } = yield getSources(threadClient);
+ let source = findSource(sources, SOURCE_URL);
+ let sourceClient = threadClient.source(source);
+
+ let location = { line: 6, column: 17 };
+ let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location);
+ do_check_true(packet.isPending);
+ do_check_false("actualLocation" in packet);
+
+ packet = yield executeOnNextTickAndWaitForPause(function () {
+ reload(tabClient).then(function () {
+ loadSubScript(SOURCE_URL, global);
+ });
+ }, client);
+ do_check_eq(packet.type, "paused");
+ let why = packet.why;
+ do_check_eq(why.type, "breakpoint");
+ do_check_eq(why.actors.length, 1);
+ do_check_eq(why.actors[0], breakpointClient.actor);
+ let frame = packet.frame;
+ let where = frame.where;
+ do_check_eq(where.source.actor, source.actor);
+ do_check_eq(where.line, location.line);
+ do_check_eq(where.column, location.column);
+ let variables = frame.environment.bindings.variables;
+ do_check_eq(variables.a.value, 1);
+ do_check_eq(variables.b.value.type, "undefined");
+ do_check_eq(variables.c.value.type, "undefined");
+ yield resume(threadClient);
+
+ yield close(client);
+ do_test_finished();
+ });
+}
diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js b/devtools/server/tests/unit/test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js
new file mode 100644
index 000000000..6185cf589
--- /dev/null
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js
@@ -0,0 +1,39 @@
+"use strict";
+
+var SOURCE_URL = getFileUrl("setBreakpoint-on-column-with-no-offsets-at-end-of-line.js");
+
+function run_test() {
+ return Task.spawn(function* () {
+ do_test_pending();
+
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+ DebuggerServer.init(() => true);
+
+ let global = createTestGlobal("test");
+ DebuggerServer.addTestGlobal(global);
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let { tabs } = yield listTabs(client);
+ let tab = findTab(tabs, "test");
+ let [, tabClient] = yield attachTab(client, tab);
+ let [, threadClient] = yield attachThread(tabClient);
+ yield resume(threadClient);
+
+ let promise = waitForNewSource(threadClient, SOURCE_URL);
+ loadSubScript(SOURCE_URL, global);
+ let { source } = yield promise;
+ let sourceClient = threadClient.source(source);
+
+ let location = { line: 4, column: 23 };
+ let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location);
+ do_check_true(packet.isPending);
+ do_check_false("actualLocation" in packet);
+
+ Cu.evalInSandbox("f()", global);
+ yield close(client);
+
+ do_test_finished();
+ });
+}
diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-column.js b/devtools/server/tests/unit/test_setBreakpoint-on-column.js
new file mode 100644
index 000000000..bee9fe004
--- /dev/null
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-column.js
@@ -0,0 +1,57 @@
+"use strict";
+
+var SOURCE_URL = getFileUrl("setBreakpoint-on-column.js");
+
+function run_test() {
+ return Task.spawn(function* () {
+ do_test_pending();
+
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+ DebuggerServer.init(() => true);
+
+ let global = createTestGlobal("test");
+ DebuggerServer.addTestGlobal(global);
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let { tabs } = yield listTabs(client);
+ let tab = findTab(tabs, "test");
+ let [, tabClient] = yield attachTab(client, tab);
+ let [, threadClient] = yield attachThread(tabClient);
+ yield resume(threadClient);
+
+ let promise = waitForNewSource(threadClient, SOURCE_URL);
+ loadSubScript(SOURCE_URL, global);
+ let { source } = yield promise;
+ let sourceClient = threadClient.source(source);
+
+ let location = { line: 4, column: 17 };
+ let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location);
+ do_check_false(packet.isPending);
+ do_check_false("actualLocation" in packet);
+
+ packet = yield executeOnNextTickAndWaitForPause(function () {
+ Cu.evalInSandbox("f()", global);
+ }, client);
+ do_check_eq(packet.type, "paused");
+ let why = packet.why;
+ do_check_eq(why.type, "breakpoint");
+ do_check_eq(why.actors.length, 1);
+ do_check_eq(why.actors[0], breakpointClient.actor);
+ let frame = packet.frame;
+ let where = frame.where;
+ do_check_eq(where.source.actor, source.actor);
+ do_check_eq(where.line, location.line);
+ do_check_eq(where.column, location.column);
+ let variables = frame.environment.bindings.variables;
+ do_check_eq(variables.a.value, 1);
+ do_check_eq(variables.b.value.type, "undefined");
+ do_check_eq(variables.c.value.type, "undefined");
+
+ yield resume(threadClient);
+ yield close(client);
+
+ do_test_finished();
+ });
+}
diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-in-gcd-script.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-in-gcd-script.js
new file mode 100644
index 000000000..8479c797e
--- /dev/null
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-in-gcd-script.js
@@ -0,0 +1,57 @@
+"use strict";
+
+var SOURCE_URL = getFileUrl("setBreakpoint-on-line-in-gcd-script.js");
+
+function run_test() {
+ return Task.spawn(function* () {
+ do_test_pending();
+
+ let global = createTestGlobal("test");
+ loadSubScript(SOURCE_URL, global);
+ Cu.forceGC(); Cu.forceGC(); Cu.forceGC();
+
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+ DebuggerServer.init(() => true);
+ DebuggerServer.addTestGlobal(global);
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let { tabs } = yield listTabs(client);
+ let tab = findTab(tabs, "test");
+ let [, tabClient] = yield attachTab(client, tab);
+ let [, threadClient] = yield attachThread(tabClient);
+ yield resume(threadClient);
+
+ let { sources } = yield getSources(threadClient);
+ let source = findSource(sources, SOURCE_URL);
+ let sourceClient = threadClient.source(source);
+
+ let location = { line: 7 };
+ let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location);
+ do_check_true(packet.isPending);
+ do_check_false("actualLocation" in packet);
+
+ packet = yield executeOnNextTickAndWaitForPause(function () {
+ reload(tabClient).then(function () {
+ loadSubScript(SOURCE_URL, global);
+ });
+ }, client);
+ do_check_eq(packet.type, "paused");
+ let why = packet.why;
+ do_check_eq(why.type, "breakpoint");
+ do_check_eq(why.actors.length, 1);
+ do_check_eq(why.actors[0], breakpointClient.actor);
+ let frame = packet.frame;
+ let where = frame.where;
+ do_check_eq(where.source.actor, source.actor);
+ do_check_eq(where.line, location.line);
+ let variables = frame.environment.bindings.variables;
+ do_check_eq(variables.a.value, 1);
+ do_check_eq(variables.b.value.type, "undefined");
+ do_check_eq(variables.c.value.type, "undefined");
+ yield resume(threadClient);
+
+ yield close(client);
+ do_test_finished();
+ });
+}
diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js
new file mode 100644
index 000000000..2f5c1b9aa
--- /dev/null
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-offsets.js
@@ -0,0 +1,70 @@
+"use strict";
+
+var SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-multiple-offsets.js");
+
+function run_test() {
+ return Task.spawn(function* () {
+ do_test_pending();
+
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+ DebuggerServer.init(() => true);
+
+ let global = createTestGlobal("test");
+ DebuggerServer.addTestGlobal(global);
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let { tabs } = yield listTabs(client);
+ let tab = findTab(tabs, "test");
+ let [, tabClient] = yield attachTab(client, tab);
+
+ let [, threadClient] = yield attachThread(tabClient);
+ yield resume(threadClient);
+
+ let promise = waitForNewSource(threadClient, SOURCE_URL);
+ loadSubScript(SOURCE_URL, global);
+ let { source } = yield promise;
+ let sourceClient = threadClient.source(source);
+
+ let location = { line: 4 };
+ let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location);
+ do_check_false(packet.isPending);
+ do_check_false("actualLocation" in packet);
+
+ packet = yield executeOnNextTickAndWaitForPause(function () {
+ Cu.evalInSandbox("f()", global);
+ }, client);
+ do_check_eq(packet.type, "paused");
+ let why = packet.why;
+ do_check_eq(why.type, "breakpoint");
+ do_check_eq(why.actors.length, 1);
+ do_check_eq(why.actors[0], breakpointClient.actor);
+ let frame = packet.frame;
+ let where = frame.where;
+ do_check_eq(where.source.actor, source.actor);
+ do_check_eq(where.line, location.line);
+ let variables = frame.environment.bindings.variables;
+ do_check_eq(variables.i.value.type, "undefined");
+
+ packet = yield executeOnNextTickAndWaitForPause(function () {
+ resume(threadClient);
+ }, client);
+ do_check_eq(packet.type, "paused");
+ why = packet.why;
+ do_check_eq(why.type, "breakpoint");
+ do_check_eq(why.actors.length, 1);
+ do_check_eq(why.actors[0], breakpointClient.actor);
+ frame = packet.frame;
+ where = frame.where;
+ do_check_eq(where.source.actor, source.actor);
+ do_check_eq(where.line, location.line);
+ variables = frame.environment.bindings.variables;
+ do_check_eq(variables.i.value, 0);
+
+ yield resume(threadClient);
+ yield close(client);
+
+ do_test_finished();
+ });
+}
diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-statements.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-statements.js
new file mode 100644
index 000000000..104152441
--- /dev/null
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-multiple-statements.js
@@ -0,0 +1,57 @@
+"use strict";
+
+var SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-multiple-statements.js");
+
+function run_test() {
+ return Task.spawn(function* () {
+ do_test_pending();
+
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+ DebuggerServer.init(() => true);
+
+ let global = createTestGlobal("test");
+ DebuggerServer.addTestGlobal(global);
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let { tabs } = yield listTabs(client);
+ let tab = findTab(tabs, "test");
+ let [, tabClient] = yield attachTab(client, tab);
+
+ let [, threadClient] = yield attachThread(tabClient);
+ yield resume(threadClient);
+
+ let promise = waitForNewSource(threadClient, SOURCE_URL);
+ loadSubScript(SOURCE_URL, global);
+ let { source } = yield promise;
+ let sourceClient = threadClient.source(source);
+
+ let location = { line: 4 };
+ let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location);
+ do_check_false(packet.isPending);
+ do_check_false("actualLocation" in packet);
+
+ packet = yield executeOnNextTickAndWaitForPause(function () {
+ Cu.evalInSandbox("f()", global);
+ }, client);
+ do_check_eq(packet.type, "paused");
+ let why = packet.why;
+ do_check_eq(why.type, "breakpoint");
+ do_check_eq(why.actors.length, 1);
+ do_check_eq(why.actors[0], breakpointClient.actor);
+ let frame = packet.frame;
+ let where = frame.where;
+ do_check_eq(where.source.actor, source.actor);
+ do_check_eq(where.line, location.line);
+ let variables = frame.environment.bindings.variables;
+ do_check_eq(variables.a.value.type, "undefined");
+ do_check_eq(variables.b.value.type, "undefined");
+ do_check_eq(variables.c.value.type, "undefined");
+
+ yield resume(threadClient);
+ yield close(client);
+
+ do_test_finished();
+ });
+}
diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js
new file mode 100644
index 000000000..2e841fe19
--- /dev/null
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js
@@ -0,0 +1,58 @@
+"use strict";
+
+var SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-no-offsets-in-gcd-script.js");
+
+function run_test() {
+ return Task.spawn(function* () {
+ do_test_pending();
+
+ let global = createTestGlobal("test");
+ loadSubScript(SOURCE_URL, global);
+ Cu.forceGC(); Cu.forceGC(); Cu.forceGC();
+
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+ DebuggerServer.init(() => true);
+ DebuggerServer.addTestGlobal(global);
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let { tabs } = yield listTabs(client);
+ let tab = findTab(tabs, "test");
+ let [, tabClient] = yield attachTab(client, tab);
+ let [, threadClient] = yield attachThread(tabClient);
+ yield resume(threadClient);
+
+ let { sources } = yield getSources(threadClient);
+ let source = findSource(sources, SOURCE_URL);
+ let sourceClient = threadClient.source(source);
+
+ let location = { line: 7 };
+ let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location);
+ do_check_true(packet.isPending);
+ do_check_false("actualLocation" in packet);
+
+ packet = yield executeOnNextTickAndWaitForPause(function () {
+ reload(tabClient).then(function () {
+ loadSubScript(SOURCE_URL, global);
+ });
+ }, client);
+ do_check_eq(packet.type, "paused");
+ let why = packet.why;
+ do_check_eq(why.type, "breakpoint");
+ do_check_eq(why.actors.length, 1);
+ do_check_eq(why.actors[0], breakpointClient.actor);
+ let frame = packet.frame;
+ let where = frame.where;
+ do_check_eq(where.source.actor, source.actor);
+ do_check_eq(where.line, 8);
+ let variables = frame.environment.bindings.variables;
+ do_check_eq(variables.a.value, 1);
+ do_check_eq(variables.c.value.type, "undefined");
+
+ yield resume(threadClient);
+ yield close(client);
+
+ do_test_finished();
+ });
+}
diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets.js b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets.js
new file mode 100644
index 000000000..5959b23ef
--- /dev/null
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-line-with-no-offsets.js
@@ -0,0 +1,57 @@
+"use strict";
+
+var SOURCE_URL = getFileUrl("setBreakpoint-on-line-with-no-offsets.js");
+
+function run_test() {
+ return Task.spawn(function* () {
+ do_test_pending();
+
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+ DebuggerServer.init(() => true);
+
+ let global = createTestGlobal("test");
+ DebuggerServer.addTestGlobal(global);
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let { tabs } = yield listTabs(client);
+ let tab = findTab(tabs, "test");
+ let [, tabClient] = yield attachTab(client, tab);
+ let [, threadClient] = yield attachThread(tabClient);
+ yield resume(threadClient);
+
+ let promise = waitForNewSource(threadClient, SOURCE_URL);
+ loadSubScript(SOURCE_URL, global);
+ let { source } = yield promise;
+ let sourceClient = threadClient.source(source);
+
+ let location = { line: 5 };
+ let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location);
+ do_check_false(packet.isPending);
+ do_check_true("actualLocation" in packet);
+ let actualLocation = packet.actualLocation;
+ do_check_eq(actualLocation.line, 6);
+
+ packet = yield executeOnNextTickAndWaitForPause(function () {
+ Cu.evalInSandbox("f()", global);
+ }, client);
+ do_check_eq(packet.type, "paused");
+ let why = packet.why;
+ do_check_eq(why.type, "breakpoint");
+ do_check_eq(why.actors.length, 1);
+ do_check_eq(why.actors[0], breakpointClient.actor);
+ let frame = packet.frame;
+ let where = frame.where;
+ do_check_eq(where.source.actor, source.actor);
+ do_check_eq(where.line, actualLocation.line);
+ let variables = frame.environment.bindings.variables;
+ do_check_eq(variables.a.value, 1);
+ do_check_eq(variables.c.value.type, "undefined");
+
+ yield resume(threadClient);
+ yield close(client);
+
+ do_test_finished();
+ });
+}
diff --git a/devtools/server/tests/unit/test_setBreakpoint-on-line.js b/devtools/server/tests/unit/test_setBreakpoint-on-line.js
new file mode 100644
index 000000000..1dab6a633
--- /dev/null
+++ b/devtools/server/tests/unit/test_setBreakpoint-on-line.js
@@ -0,0 +1,57 @@
+"use strict";
+
+var SOURCE_URL = getFileUrl("setBreakpoint-on-line.js");
+
+function run_test() {
+ return Task.spawn(function* () {
+ do_test_pending();
+
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+ DebuggerServer.init(() => true);
+
+ let global = createTestGlobal("test");
+ DebuggerServer.addTestGlobal(global);
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ yield connect(client);
+
+ let { tabs } = yield listTabs(client);
+ let tab = findTab(tabs, "test");
+ let [, tabClient] = yield attachTab(client, tab);
+
+ let [, threadClient] = yield attachThread(tabClient);
+ yield resume(threadClient);
+
+ let promise = waitForNewSource(threadClient, SOURCE_URL);
+ loadSubScript(SOURCE_URL, global);
+ let { source } = yield promise;
+ let sourceClient = threadClient.source(source);
+
+ let location = { line: 5 };
+ let [packet, breakpointClient] = yield setBreakpoint(sourceClient, location);
+ do_check_false(packet.isPending);
+ do_check_false("actualLocation" in packet);
+
+ packet = yield executeOnNextTickAndWaitForPause(function () {
+ Cu.evalInSandbox("f()", global);
+ }, client);
+ do_check_eq(packet.type, "paused");
+ let why = packet.why;
+ do_check_eq(why.type, "breakpoint");
+ do_check_eq(why.actors.length, 1);
+ do_check_eq(why.actors[0], breakpointClient.actor);
+ let frame = packet.frame;
+ let where = frame.where;
+ do_check_eq(where.source.actor, source.actor);
+ do_check_eq(where.line, location.line);
+ let variables = frame.environment.bindings.variables;
+ do_check_eq(variables.a.value, 1);
+ do_check_eq(variables.b.value.type, "undefined");
+ do_check_eq(variables.c.value.type, "undefined");
+
+ yield resume(threadClient);
+ yield close(client);
+
+ do_test_finished();
+ });
+}
diff --git a/devtools/server/tests/unit/test_source-01.js b/devtools/server/tests/unit/test_source-01.js
new file mode 100644
index 000000000..3401cc660
--- /dev/null
+++ b/devtools/server/tests/unit/test_source-01.js
@@ -0,0 +1,78 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+// This test ensures that we can create SourceActors and SourceClients properly,
+// and that they can communicate over the protocol to fetch the source text for
+// a given script.
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+ Cu.evalInSandbox(
+ "" + function stopMe(arg1) {
+ debugger;
+ },
+ gDebuggee,
+ "1.8",
+ getFileUrl("test_source-01.js")
+ );
+
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_source();
+ });
+ });
+ do_test_pending();
+}
+
+const SOURCE_URL = "http://example.com/foobar.js";
+const SOURCE_CONTENT = "stopMe()";
+
+function test_source()
+{
+ DebuggerServer.LONG_STRING_LENGTH = 200;
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.getSources(function (aResponse) {
+ do_check_true(!!aResponse);
+ do_check_true(!!aResponse.sources);
+
+ let source = aResponse.sources.filter(function (s) {
+ return s.url === SOURCE_URL;
+ })[0];
+
+ do_check_true(!!source);
+
+ let sourceClient = gThreadClient.source(source);
+ sourceClient.source(function (aResponse) {
+ do_check_true(!!aResponse);
+ do_check_true(!aResponse.error);
+ do_check_true(!!aResponse.contentType);
+ do_check_true(aResponse.contentType.includes("javascript"));
+
+ do_check_true(!!aResponse.source);
+ do_check_eq(SOURCE_CONTENT,
+ aResponse.source);
+
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+ });
+
+ Cu.evalInSandbox(
+ SOURCE_CONTENT,
+ gDebuggee,
+ "1.8",
+ SOURCE_URL
+ );
+}
diff --git a/devtools/server/tests/unit/test_sourcemaps-01.js b/devtools/server/tests/unit/test_sourcemaps-01.js
new file mode 100644
index 000000000..4d92bf9cc
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-01.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check basic source map integration with the "newSource" packet in the RDP.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+const {SourceNode} = require("source-map");
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-source-map");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_source_map();
+ });
+ });
+ do_test_pending();
+}
+
+function test_simple_source_map()
+{
+ // Because we are source mapping, we should be notified of a.js, b.js, and
+ // c.js as sources, and shouldn't receive abc.js or test_sourcemaps-01.js.
+ let expectedSources = new Set(["http://example.com/www/js/a.js",
+ "http://example.com/www/js/b.js",
+ "http://example.com/www/js/c.js"]);
+
+ gThreadClient.addListener("newSource", function _onNewSource(aEvent, aPacket) {
+ do_check_eq(aEvent, "newSource");
+ do_check_eq(aPacket.type, "newSource");
+ do_check_true(!!aPacket.source);
+
+ do_check_true(expectedSources.has(aPacket.source.url),
+ "The source url should be one of our original sources.");
+ expectedSources.delete(aPacket.source.url);
+
+ if (expectedSources.size === 0) {
+ gClient.removeListener("newSource", _onNewSource);
+ finishClient(gClient);
+ }
+ });
+
+ let { code, map } = (new SourceNode(null, null, null, [
+ new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"),
+ new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"),
+ new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"),
+ ])).toStringWithSourceMap({
+ file: "abc.js",
+ sourceRoot: "http://example.com/www/js/"
+ });
+
+ code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString());
+
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+ "http://example.com/www/js/abc.js", 1);
+}
diff --git a/devtools/server/tests/unit/test_sourcemaps-02.js b/devtools/server/tests/unit/test_sourcemaps-02.js
new file mode 100644
index 000000000..2a343afaa
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-02.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check basic source map integration with the "sources" packet in the RDP.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+const {SourceNode} = require("source-map");
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-source-map");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_source_map();
+ });
+ });
+ do_test_pending();
+}
+
+function test_simple_source_map()
+{
+ // Because we are source mapping, we should be notified of a.js, b.js, and
+ // c.js as sources, and shouldn"t receive abc.js or test_sourcemaps-01.js.
+ let expectedSources = new Set(["http://example.com/www/js/a.js",
+ "http://example.com/www/js/b.js",
+ "http://example.com/www/js/c.js"]);
+
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.getSources(function (aResponse) {
+ do_check_true(!aResponse.error, "Should not get an error");
+
+ for (let s of aResponse.sources) {
+ do_check_neq(s.url, "http://example.com/www/js/abc.js",
+ "Shouldn't get the generated source's url.");
+ expectedSources.delete(s.url);
+ }
+
+ do_check_eq(expectedSources.size, 0,
+ "Should have found all the expected sources sources by now.");
+
+ finishClient(gClient);
+ });
+ });
+
+ let { code, map } = (new SourceNode(null, null, null, [
+ new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"),
+ new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"),
+ new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"),
+ new SourceNode(1, 0, "d.js", "debugger;\n")
+ ])).toStringWithSourceMap({
+ file: "abc.js",
+ sourceRoot: "http://example.com/www/js/"
+ });
+
+ code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString());
+
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+ "http://example.com/www/js/abc.js", 1);
+}
diff --git a/devtools/server/tests/unit/test_sourcemaps-03.js b/devtools/server/tests/unit/test_sourcemaps-03.js
new file mode 100644
index 000000000..2fbd99aad
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-03.js
@@ -0,0 +1,137 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check setting breakpoints in source mapped sources.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+const {SourceNode} = require("source-map");
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-source-map");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_source_map();
+ });
+ });
+ do_test_pending();
+}
+
+function testBreakpointMapping(aName, aCallback)
+{
+ Task.spawn(function* () {
+ let response = yield waitForPause(gThreadClient);
+ do_check_eq(response.why.type, "debuggerStatement");
+
+ const source = yield getSource(gThreadClient, "http://example.com/www/js/" + aName + ".js");
+ response = yield setBreakpoint(source, {
+ // Setting the breakpoint on an empty line so that it is pushed down one
+ // line and we can check the source mapped actualLocation later.
+ line: 3
+ });
+
+ // Should not slide breakpoints for sourcemapped sources
+ do_check_true(!response.actualLocation);
+
+ yield setBreakpoint(source, { line: 4 });
+
+ // The eval will cause us to resume, then we get an unsolicited pause
+ // because of our breakpoint, we resume again to finish the eval, and
+ // finally receive our last pause which has the result of the client
+ // evaluation.
+ response = yield gThreadClient.eval(null, aName + "()");
+ do_check_eq(response.type, "resumed");
+
+ response = yield waitForPause(gThreadClient);
+ do_check_eq(response.why.type, "breakpoint");
+ // Assert that we paused because of the breakpoint at the correct
+ // location in the code by testing that the value of `ret` is still
+ // undefined.
+ do_check_eq(response.frame.environment.bindings.variables.ret.value.type,
+ "undefined");
+
+ response = yield resume(gThreadClient);
+
+ response = yield waitForPause(gThreadClient);
+ do_check_eq(response.why.type, "clientEvaluated");
+ do_check_eq(response.why.frameFinished.return, aName);
+
+ response = yield resume(gThreadClient);
+
+ aCallback();
+ });
+
+ gDebuggee.eval("(" + function () {
+ debugger;
+ } + "());");
+}
+
+function test_simple_source_map()
+{
+ let expectedSources = new Set([
+ "http://example.com/www/js/a.js",
+ "http://example.com/www/js/b.js",
+ "http://example.com/www/js/c.js"
+ ]);
+
+ gThreadClient.addListener("newSource", function _onNewSource(aEvent, aPacket) {
+ expectedSources.delete(aPacket.source.url);
+ if (expectedSources.size > 0) {
+ return;
+ }
+ gThreadClient.removeListener("newSource", _onNewSource);
+
+ testBreakpointMapping("a", function () {
+ testBreakpointMapping("b", function () {
+ testBreakpointMapping("c", function () {
+ finishClient(gClient);
+ });
+ });
+ });
+ });
+
+ let a = new SourceNode(null, null, null, [
+ new SourceNode(1, 0, "a.js", "function a() {\n"),
+ new SourceNode(2, 0, "a.js", " var ret;\n"),
+ new SourceNode(3, 0, "a.js", " // Empty line\n"),
+ new SourceNode(4, 0, "a.js", " ret = 'a';\n"),
+ new SourceNode(5, 0, "a.js", " return ret;\n"),
+ new SourceNode(6, 0, "a.js", "}\n")
+ ]);
+ let b = new SourceNode(null, null, null, [
+ new SourceNode(1, 0, "b.js", "function b() {\n"),
+ new SourceNode(2, 0, "b.js", " var ret;\n"),
+ new SourceNode(3, 0, "b.js", " // Empty line\n"),
+ new SourceNode(4, 0, "b.js", " ret = 'b';\n"),
+ new SourceNode(5, 0, "b.js", " return ret;\n"),
+ new SourceNode(6, 0, "b.js", "}\n")
+ ]);
+ let c = new SourceNode(null, null, null, [
+ new SourceNode(1, 0, "c.js", "function c() {\n"),
+ new SourceNode(2, 0, "c.js", " var ret;\n"),
+ new SourceNode(3, 0, "c.js", " // Empty line\n"),
+ new SourceNode(4, 0, "c.js", " ret = 'c';\n"),
+ new SourceNode(5, 0, "c.js", " return ret;\n"),
+ new SourceNode(6, 0, "c.js", "}\n")
+ ]);
+
+ let { code, map } = (new SourceNode(null, null, null, [
+ a, b, c
+ ])).toStringWithSourceMap({
+ file: "http://example.com/www/js/abc.js",
+ sourceRoot: "http://example.com/www/js/"
+ });
+
+ code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString());
+
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+ "http://example.com/www/js/abc.js", 1);
+}
diff --git a/devtools/server/tests/unit/test_sourcemaps-04.js b/devtools/server/tests/unit/test_sourcemaps-04.js
new file mode 100644
index 000000000..5fecb44c8
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-04.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that absolute source map urls work.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-source-map");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_absolute_source_map();
+ });
+ });
+ do_test_pending();
+}
+
+function test_absolute_source_map()
+{
+ gThreadClient.addOneTimeListener("newSource", function _onNewSource(aEvent, aPacket) {
+ do_check_eq(aEvent, "newSource");
+ do_check_eq(aPacket.type, "newSource");
+ do_check_true(!!aPacket.source);
+
+ do_check_true(aPacket.source.url.indexOf("sourcemapped.coffee") !== -1,
+ "The new source should be a coffee file.");
+ do_check_eq(aPacket.source.url.indexOf("sourcemapped.js"), -1,
+ "The new source should not be a js file.");
+
+ finishClient(gClient);
+ });
+
+ code = readFile("sourcemapped.js")
+ + "\n//# sourceMappingURL=" + getFileUrl("source-map-data/sourcemapped.map");
+
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+ getFileUrl("sourcemapped.js"), 1);
+}
diff --git a/devtools/server/tests/unit/test_sourcemaps-05.js b/devtools/server/tests/unit/test_sourcemaps-05.js
new file mode 100644
index 000000000..edc9178db
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-05.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that relative source map urls work.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-source-map");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_relative_source_map();
+ });
+ });
+ do_test_pending();
+}
+
+function test_relative_source_map()
+{
+ gThreadClient.addOneTimeListener("newSource", function _onNewSource(aEvent, aPacket) {
+ do_check_eq(aEvent, "newSource");
+ do_check_eq(aPacket.type, "newSource");
+ do_check_true(!!aPacket.source);
+
+ do_check_true(aPacket.source.url.indexOf("sourcemapped.coffee") !== -1,
+ "The new source should be a coffee file.");
+ do_check_eq(aPacket.source.url.indexOf("sourcemapped.js"), -1,
+ "The new source should not be a js file.");
+
+ finishClient(gClient);
+ });
+
+ code = readFile("sourcemapped.js")
+ + "\n//# sourceMappingURL=source-map-data/sourcemapped.map";
+
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+ getFileUrl("sourcemapped.js"), 1);
+}
diff --git a/devtools/server/tests/unit/test_sourcemaps-06.js b/devtools/server/tests/unit/test_sourcemaps-06.js
new file mode 100644
index 000000000..41b518753
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-06.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that we can load sources whose content is embedded in the
+ * "sourcesContent" field of a source map.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+const {SourceNode} = require("source-map");
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-source-map");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_source_content();
+ });
+ });
+ do_test_pending();
+}
+
+function test_source_content()
+{
+ let numNewSources = 0;
+
+ gThreadClient.addListener("newSource", function _onNewSource(aEvent, aPacket) {
+ if (++numNewSources !== 3) {
+ return;
+ }
+ gThreadClient.removeListener("newSource", _onNewSource);
+
+ gThreadClient.getSources(function (aResponse) {
+ do_check_true(!aResponse.error, "Should not get an error");
+
+ testContents(aResponse.sources, 0, (timesCalled) => {
+ do_check_eq(timesCalled, 3);
+ finishClient(gClient);
+ });
+ });
+ });
+
+ let node = new SourceNode(null, null, null, [
+ new SourceNode(1, 0, "a.js", "function a() { return 'a'; }\n"),
+ new SourceNode(1, 0, "b.js", "function b() { return 'b'; }\n"),
+ new SourceNode(1, 0, "c.js", "function c() { return 'c'; }\n"),
+ ]);
+
+ node.setSourceContent("a.js", "content for a.js");
+ node.setSourceContent("b.js", "content for b.js");
+ node.setSourceContent("c.js", "content for c.js");
+
+ let { code, map } = node.toStringWithSourceMap({
+ file: "abc.js"
+ });
+
+ code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString());
+
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+ "http://example.com/www/js/abc.js", 1);
+}
+
+function testContents(sources, timesCalled, callback) {
+ if (sources.length === 0) {
+ callback(timesCalled);
+ return;
+ }
+
+
+ let source = sources[0];
+ let sourceClient = gThreadClient.source(sources[0]);
+
+ if (sourceClient.url) {
+ sourceClient.source((aResponse) => {
+ do_check_true(!aResponse.error,
+ "Should not get an error loading the source from sourcesContent");
+
+ let expectedContent = "content for " + source.url;
+ do_check_eq(aResponse.source, expectedContent,
+ "Should have the expected source content");
+
+ testContents(sources.slice(1), timesCalled + 1, callback);
+ });
+ }
+ else {
+ testContents(sources.slice(1), timesCalled, callback);
+ }
+}
diff --git a/devtools/server/tests/unit/test_sourcemaps-07.js b/devtools/server/tests/unit/test_sourcemaps-07.js
new file mode 100644
index 000000000..b8a9462c0
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-07.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we don't permanently cache sources from source maps.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+const {SourceNode} = require("source-map");
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-source-map");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_cached_original_sources();
+ });
+ });
+ do_test_pending();
+}
+
+function test_cached_original_sources()
+{
+ writeFile("temp.js", "initial content");
+
+ gThreadClient.addOneTimeListener("newSource", onNewSource);
+
+ let node = new SourceNode(1, 0,
+ getFileUrl("temp.js"),
+ "function funcFromTemp() {}\n");
+ let { code, map } = node.toStringWithSourceMap({
+ file: "abc.js"
+ });
+ code += "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString());
+
+
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+ "http://example.com/www/js/abc.js", 1);
+}
+
+function onNewSource(aEvent, aPacket) {
+ let sourceClient = gThreadClient.source(aPacket.source);
+ sourceClient.source(function (aResponse) {
+ do_check_true(!aResponse.error,
+ "Should not be an error grabbing the source");
+ do_check_eq(aResponse.source, "initial content",
+ "The correct source content should be sent");
+
+ writeFile("temp.js", "new content");
+
+ sourceClient.source(function (aResponse) {
+ do_check_true(!aResponse.error,
+ "Should not be an error grabbing the source");
+ do_check_eq(aResponse.source, "new content",
+ "The correct source content should not be cached, so we should get the new content");
+
+ do_get_file("temp.js").remove(false);
+ finishClient(gClient);
+ });
+ });
+}
diff --git a/devtools/server/tests/unit/test_sourcemaps-08.js b/devtools/server/tests/unit/test_sourcemaps-08.js
new file mode 100644
index 000000000..b23665e43
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-08.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Regression test for bug 882986 regarding sourcesContent and absolute source
+ * URLs.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-source-map");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_source_maps();
+ });
+ });
+ do_test_pending();
+}
+
+function test_source_maps()
+{
+ gThreadClient.addOneTimeListener("newSource", function (aEvent, aPacket) {
+ let sourceClient = gThreadClient.source(aPacket.source);
+ sourceClient.source(function ({error, source}) {
+ do_check_true(!error, "should be able to grab the source");
+ do_check_eq(source, "foo",
+ "Should load the source from the sourcesContent field");
+ finishClient(gClient);
+ });
+ });
+
+ let code = "'nothing here';\n";
+ code += "//# sourceMappingURL=data:text/json," + JSON.stringify({
+ version: 3,
+ file: "foo.js",
+ sources: ["/a"],
+ names: [],
+ mappings: "AACA",
+ sourcesContent: ["foo"]
+ });
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+ "http://example.com/foo.js", 1);
+}
diff --git a/devtools/server/tests/unit/test_sourcemaps-09.js b/devtools/server/tests/unit/test_sourcemaps-09.js
new file mode 100644
index 000000000..c317cf723
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-09.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that source maps and breakpoints work with minified javascript.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-source-map");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_minified();
+ });
+ });
+ do_test_pending();
+}
+
+function test_minified()
+{
+ let newSourceFired = false;
+
+ gThreadClient.addOneTimeListener("newSource", function _onNewSource(aEvent, aPacket) {
+ do_check_eq(aEvent, "newSource");
+ do_check_eq(aPacket.type, "newSource");
+ do_check_true(!!aPacket.source);
+
+ do_check_eq(aPacket.source.url, "http://example.com/foo.js",
+ "The new source should be foo.js");
+ do_check_eq(aPacket.source.url.indexOf("foo.min.js"), -1,
+ "The new source should not be the minified file");
+
+ newSourceFired = true;
+ });
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aEvent, "paused");
+ do_check_eq(aPacket.why.type, "debuggerStatement");
+
+ let location = {
+ line: 5
+ };
+
+ getSource(gThreadClient, "http://example.com/foo.js").then(source => {
+ source.setBreakpoint(location, function (aResponse, bpClient) {
+ do_check_true(!aResponse.error);
+ testHitBreakpoint();
+ });
+ });
+ });
+
+ // This is the original foo.js, which was then minified with uglifyjs version
+ // 2.2.5 and the "--mangle" option.
+ //
+ // (function () {
+ // debugger;
+ // function foo(n) {
+ // var bar = n + n;
+ // var unused = null;
+ // return bar;
+ // }
+ // for (var i = 0; i < 10; i++) {
+ // foo(i);
+ // }
+ // }());
+
+ let code = '(function(){debugger;function r(r){var n=r+r;var u=null;return n}for(var n=0;n<10;n++){r(n)}})();\n//# sourceMappingURL=data:text/json,{"file":"foo.min.js","version":3,"sources":["foo.js"],"names":["foo","n","bar","unused","i"],"mappings":"CAAC,WACC,QACA,SAASA,GAAIC,GACX,GAAIC,GAAMD,EAAIA,CACd,IAAIE,GAAS,IACb,OAAOD,GAET,IAAK,GAAIE,GAAI,EAAGA,EAAI,GAAIA,IAAK,CAC3BJ,EAAII"}';
+
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+ "http://example.com/foo.min.js", 1);
+}
+
+function testHitBreakpoint(timesHit = 0) {
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ ++timesHit;
+
+ do_check_eq(aEvent, "paused");
+ do_check_eq(aPacket.why.type, "breakpoint");
+
+ if (timesHit === 10) {
+ gThreadClient.resume(() => finishClient(gClient));
+ } else {
+ testHitBreakpoint(timesHit);
+ }
+ });
+
+ gThreadClient.resume();
+}
diff --git a/devtools/server/tests/unit/test_sourcemaps-10.js b/devtools/server/tests/unit/test_sourcemaps-10.js
new file mode 100644
index 000000000..e955dc8fb
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-10.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that we source map frame locations for the frame we are paused at.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+const {SourceNode} = require("source-map");
+
+function run_test() {
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-source-map");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ promise.resolve(define_code())
+ .then(run_code)
+ .then(test_frame_location)
+ .then(null, error => {
+ dump(error + "\n");
+ dump(error.stack);
+ do_check_true(false);
+ })
+ .then(() => {
+ finishClient(gClient);
+ });
+ });
+ });
+ do_test_pending();
+}
+
+function define_code() {
+ let { code, map } = (new SourceNode(null, null, null, [
+ new SourceNode(1, 0, "a.js", "function a() {\n"),
+ new SourceNode(2, 0, "a.js", " b();\n"),
+ new SourceNode(3, 0, "a.js", "}\n"),
+ new SourceNode(1, 0, "b.js", "function b() {\n"),
+ new SourceNode(2, 0, "b.js", " c();\n"),
+ new SourceNode(3, 0, "b.js", "}\n"),
+ new SourceNode(1, 0, "c.js", "function c() {\n"),
+ new SourceNode(2, 0, "c.js", " debugger;\n"),
+ new SourceNode(3, 0, "c.js", "}\n"),
+ ])).toStringWithSourceMap({
+ file: "abc.js",
+ sourceRoot: "http://example.com/www/js/"
+ });
+
+ code += "//# sourceMappingURL=data:text/json," + map.toString();
+
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+ "http://example.com/www/js/abc.js", 1);
+}
+
+function run_code() {
+ const d = promise.defer();
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ d.resolve(aPacket);
+ gThreadClient.resume();
+ });
+ gDebuggee.a();
+ return d.promise;
+}
+
+function test_frame_location({ frame: { where: { source, line, column } } }) {
+ do_check_eq(source.url, "http://example.com/www/js/c.js");
+ do_check_eq(line, 2);
+ do_check_eq(column, 0);
+}
diff --git a/devtools/server/tests/unit/test_sourcemaps-11.js b/devtools/server/tests/unit/test_sourcemaps-11.js
new file mode 100644
index 000000000..e598c4c09
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-11.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that we source map frame locations returned by "frames" requests.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+const {SourceNode} = require("source-map");
+
+function run_test() {
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-source-map");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ promise.resolve(define_code())
+ .then(run_code)
+ .then(test_frames)
+ .then(null, error => {
+ dump(error + "\n");
+ dump(error.stack);
+ do_check_true(false);
+ })
+ .then(() => {
+ finishClient(gClient);
+ });
+ });
+ });
+ do_test_pending();
+}
+
+function define_code() {
+ let { code, map } = (new SourceNode(null, null, null, [
+ new SourceNode(1, 0, "a.js", "function a() {\n"),
+ new SourceNode(2, 0, "a.js", " b();\n"),
+ new SourceNode(3, 0, "a.js", "}\n"),
+ new SourceNode(1, 0, "b.js", "function b() {\n"),
+ new SourceNode(2, 0, "b.js", " c();\n"),
+ new SourceNode(3, 0, "b.js", "}\n"),
+ new SourceNode(1, 0, "c.js", "function c() {\n"),
+ new SourceNode(2, 0, "c.js", " debugger;\n"),
+ new SourceNode(3, 0, "c.js", "}\n"),
+ ])).toStringWithSourceMap({
+ file: "abc.js",
+ sourceRoot: "http://example.com/www/js/"
+ });
+
+ code += "//# sourceMappingURL=data:text/json," + map.toString();
+
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+ "http://example.com/www/js/abc.js", 1);
+}
+
+function run_code() {
+ const d = promise.defer();
+ gClient.addOneTimeListener("paused", function () {
+ gThreadClient.getFrames(0, 3, function (aResponse) {
+ d.resolve(aResponse);
+ gThreadClient.resume();
+ });
+ });
+ gDebuggee.a();
+ return d.promise;
+}
+
+function test_frames({ error, frames }) {
+ do_check_true(!error);
+ do_check_eq(frames.length, 3);
+ check_frame(frames[0], "http://example.com/www/js/c.js");
+ check_frame(frames[1], "http://example.com/www/js/b.js");
+ check_frame(frames[2], "http://example.com/www/js/a.js");
+}
+
+function check_frame({ where: { source, line, column } }, aExpectedUrl) {
+ do_check_eq(source.url, aExpectedUrl);
+ do_check_eq(line, 2);
+ do_check_eq(column, 0);
+}
diff --git a/devtools/server/tests/unit/test_sourcemaps-12.js b/devtools/server/tests/unit/test_sourcemaps-12.js
new file mode 100644
index 000000000..cb7f29920
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-12.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that we continue stepping when a single original source's line
+ * corresponds to multiple generated js lines.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+const {SourceNode} = require("source-map");
+
+function run_test() {
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-source-map");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ define_code();
+ });
+ });
+ do_test_pending();
+}
+
+function define_code() {
+ let { code, map } = (new SourceNode(null, null, null, [
+ new SourceNode(1, 0, "a.js", "function runTest() {\n"),
+ // A bunch of js lines map to the same original source line.
+ new SourceNode(2, 0, "a.js", " debugger;\n"),
+ new SourceNode(2, 0, "a.js", " var sum = 0;\n"),
+ new SourceNode(2, 0, "a.js", " for (var i = 0; i < 5; i++) {\n"),
+ new SourceNode(2, 0, "a.js", " sum += i;\n"),
+ new SourceNode(2, 0, "a.js", " }\n"),
+ // And now we have a new line in the original source that we should stop at.
+ new SourceNode(3, 0, "a.js", " sum;\n"),
+ new SourceNode(3, 0, "a.js", "}\n"),
+ ])).toStringWithSourceMap({
+ file: "abc.js",
+ sourceRoot: "http://example.com/"
+ });
+
+ code += "//# sourceMappingURL=data:text/json," + map.toString();
+
+ Components.utils.evalInSandbox(code, gDebuggee, "1.8",
+ "http://example.com/abc.js", 1);
+
+ run_code();
+}
+
+function run_code() {
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aPacket.why.type, "debuggerStatement");
+ step_in();
+ });
+ gDebuggee.runTest();
+}
+
+function step_in() {
+ gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ let { frame: { environment, where: { source, line } } } = aPacket;
+ // Stepping should have moved us to the next source mapped line.
+ do_check_eq(source.url, "http://example.com/a.js");
+ do_check_eq(line, 3);
+ // Which should have skipped over the for loop in the generated js and sum
+ // should be calculated.
+ do_check_eq(environment.bindings.variables.sum.value, 10);
+ finishClient(gClient);
+ });
+ gThreadClient.stepIn();
+}
+
diff --git a/devtools/server/tests/unit/test_sourcemaps-13.js b/devtools/server/tests/unit/test_sourcemaps-13.js
new file mode 100644
index 000000000..203731fda
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-13.js
@@ -0,0 +1,105 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we don't permanently cache source maps across reloads.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gTabClient;
+
+const {SourceNode} = require("source-map");
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-source-map");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ gTabClient = aTabClient;
+ setup_code();
+ });
+ });
+ do_test_pending();
+}
+
+// The MAP_FILE_NAME is .txt so that the OS will definitely have an extension ->
+// content type mapping for the extension. If it doesn't (like .map or .json),
+// it logs console errors, which cause the test to fail. See bug 907839.
+const MAP_FILE_NAME = "temporary-generated.txt";
+
+const TEMP_FILE_1 = "temporary1.js";
+const TEMP_FILE_2 = "temporary2.js";
+const TEMP_GENERATED_SOURCE = "temporary-generated.js";
+
+function setup_code() {
+ let node = new SourceNode(1, 0,
+ getFileUrl(TEMP_FILE_1, true),
+ "function temporary1() {}\n");
+ let { code, map } = node.toStringWithSourceMap({
+ file: getFileUrl(TEMP_GENERATED_SOURCE, true)
+ });
+
+ code += "//# sourceMappingURL=" + getFileUrl(MAP_FILE_NAME, true);
+ writeFile(MAP_FILE_NAME, map.toString());
+
+ Cu.evalInSandbox(code,
+ gDebuggee,
+ "1.8",
+ getFileUrl(TEMP_GENERATED_SOURCE, true),
+ 1);
+
+ test_initial_sources();
+}
+
+function test_initial_sources() {
+ gThreadClient.getSources(function ({ error, sources }) {
+ do_check_true(!error);
+ sources = sources.filter(source => source.url);
+ do_check_eq(sources.length, 1);
+ do_check_eq(sources[0].url, getFileUrl(TEMP_FILE_1, true));
+ reload(gTabClient).then(setup_new_code);
+ });
+}
+
+function setup_new_code() {
+ let node = new SourceNode(1, 0,
+ getFileUrl(TEMP_FILE_2, true),
+ "function temporary2() {}\n");
+ let { code, map } = node.toStringWithSourceMap({
+ file: getFileUrl(TEMP_GENERATED_SOURCE, true)
+ });
+
+ code += "\n//# sourceMappingURL=" + getFileUrl(MAP_FILE_NAME, true);
+ writeFile(MAP_FILE_NAME, map.toString());
+
+ gThreadClient.addOneTimeListener("newSource", test_new_sources);
+ Cu.evalInSandbox(code,
+ gDebuggee,
+ "1.8",
+ getFileUrl(TEMP_GENERATED_SOURCE, true),
+ 1);
+}
+
+function test_new_sources() {
+ gThreadClient.getSources(function ({ error, sources }) {
+ do_check_true(!error);
+ sources = sources.filter(source => source.url);
+
+ // Should now have TEMP_FILE_2 as a source.
+ do_check_eq(sources.length, 1);
+ let s = sources.filter(s => s.url === getFileUrl(TEMP_FILE_2, true))[0];
+ do_check_true(!!s);
+
+ finish_test();
+ });
+}
+
+function finish_test() {
+ do_get_file(MAP_FILE_NAME).remove(false);
+ finishClient(gClient);
+}
diff --git a/devtools/server/tests/unit/test_sourcemaps-16.js b/devtools/server/tests/unit/test_sourcemaps-16.js
new file mode 100644
index 000000000..4df9ece23
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-16.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Verify that we can load the contents of every source in a source map produced
+ * by babel and browserify.
+ */
+
+var gDebuggee;
+var gClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-sourcemaps");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestThread(gClient, "test-sourcemaps", testSourcemap);
+ });
+ do_test_pending();
+}
+
+const testSourcemap = Task.async(function* (threadResponse, tabClient, threadClient, tabResponse) {
+ evalTestCode();
+
+ const { sources } = yield getSources(threadClient);
+
+ for (let form of sources) {
+ let sourceResponse = yield getSourceContent(threadClient.source(form));
+ ok(sourceResponse, "Should be able to get the source response");
+ ok(sourceResponse.source, "Should have the source text as well");
+ }
+
+ finishClient(gClient);
+});
+
+const TEST_FILE = "babel_and_browserify_script_with_source_map.js";
+
+function evalTestCode() {
+ const testFileContents = readFile(TEST_FILE);
+ Cu.evalInSandbox(testFileContents,
+ gDebuggee,
+ "1.8",
+ getFileUrl(TEST_FILE),
+ 1);
+}
diff --git a/devtools/server/tests/unit/test_sourcemaps-17.js b/devtools/server/tests/unit/test_sourcemaps-17.js
new file mode 100644
index 000000000..85da95972
--- /dev/null
+++ b/devtools/server/tests/unit/test_sourcemaps-17.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that we properly handle frames that cannot be sourcemapped
+ * when using sourcemaps.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+const {SourceNode} = require("source-map");
+
+function run_test() {
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-source-map");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-source-map", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_source_map();
+ });
+ });
+ do_test_pending();
+}
+
+function test_source_map() {
+ // Set up debuggee code.
+ const a = new SourceNode(1, 1, "a.js", "function a() { b(); }");
+ const b = new SourceNode(null, null, null, "function b() { c(); }");
+ const c = new SourceNode(1, 1, "c.js", "function c() { d(); }");
+ const d = new SourceNode(null, null, null, "function d() { e(); }");
+ const e = new SourceNode(1, 1, "e.js", "function e() { debugger; }");
+ const { map, code } = (new SourceNode(null, null, null, [a, b, c, d, e])).toStringWithSourceMap({
+ file: "root.js",
+ sourceRoot: "root",
+ });
+ Components.utils.evalInSandbox(
+ code + "//# sourceMappingURL=data:text/json;base64," + btoa(map.toString()),
+ gDebuggee,
+ "1.8",
+ "http://example.com/www/js/abc.js",
+ 1
+ );
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.getFrames(0, 50, function ({ error, frames }) {
+ do_check_true(!error);
+ do_check_eq(frames.length, 4);
+ // b.js should be skipped
+ do_check_eq(frames[0].where.source.url, "http://example.com/www/root/e.js");
+ do_check_eq(frames[1].where.source.url, "http://example.com/www/root/c.js");
+ do_check_eq(frames[2].where.source.url, "http://example.com/www/root/a.js");
+ do_check_eq(frames[3].where.source.url, null);
+
+ finishClient(gClient);
+ });
+ });
+
+ // Trigger it.
+ gDebuggee.eval("a()");
+}
diff --git a/devtools/server/tests/unit/test_stepping-01.js b/devtools/server/tests/unit/test_stepping-01.js
new file mode 100644
index 000000000..593a485a1
--- /dev/null
+++ b/devtools/server/tests/unit/test_stepping-01.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check basic step-over functionality.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_stepping();
+ });
+ });
+}
+
+function test_simple_stepping()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 2);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ // Check that stepping worked.
+ do_check_eq(gDebuggee.a, undefined);
+ do_check_eq(gDebuggee.b, undefined);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ // Check that stepping worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, undefined);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ // When leaving a stack frame the line number doesn't change.
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ // Check that stepping worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, 2);
+
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ gThreadClient.stepOver();
+ });
+ gThreadClient.stepOver();
+
+ });
+ gThreadClient.stepOver();
+
+ });
+
+ gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "var a = 1;\n" + // line0 + 2
+ "var b = 2;\n"); // line0 + 3
+}
diff --git a/devtools/server/tests/unit/test_stepping-02.js b/devtools/server/tests/unit/test_stepping-02.js
new file mode 100644
index 000000000..eaee099b9
--- /dev/null
+++ b/devtools/server/tests/unit/test_stepping-02.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check basic step-in functionality.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_stepping();
+ });
+ });
+}
+
+function test_simple_stepping()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 2);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ // Check that stepping worked.
+ do_check_eq(gDebuggee.a, undefined);
+ do_check_eq(gDebuggee.b, undefined);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ // Check that stepping worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, undefined);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ // When leaving a stack frame the line number doesn't change.
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ // Check that stepping worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, 2);
+
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ gThreadClient.stepIn();
+ });
+ gThreadClient.stepIn();
+
+ });
+ gThreadClient.stepIn();
+
+ });
+
+ gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "var a = 1;\n" + // line0 + 2
+ "var b = 2;\n"); // line0 + 3
+}
diff --git a/devtools/server/tests/unit/test_stepping-03.js b/devtools/server/tests/unit/test_stepping-03.js
new file mode 100644
index 000000000..6123928d7
--- /dev/null
+++ b/devtools/server/tests/unit/test_stepping-03.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check basic step-out functionality.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_stepping();
+ });
+ });
+}
+
+function test_simple_stepping()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ // Check that stepping worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, 2);
+
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ gThreadClient.stepOut();
+
+ });
+
+ gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+ "function f() {\n" + // line0 + 1
+ " debugger;\n" + // line0 + 2
+ " this.a = 1;\n" + // line0 + 3
+ " this.b = 2;\n" + // line0 + 4
+ "}\n" + // line0 + 5
+ "f();\n"); // line0 + 6
+}
diff --git a/devtools/server/tests/unit/test_stepping-04.js b/devtools/server/tests/unit/test_stepping-04.js
new file mode 100644
index 000000000..efe52200d
--- /dev/null
+++ b/devtools/server/tests/unit/test_stepping-04.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that stepping over a function call does not pause inside the function.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_simple_stepping();
+ });
+ });
+}
+
+function test_simple_stepping()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ // Check that stepping worked.
+ do_check_eq(gDebuggee.a, undefined);
+ do_check_eq(gDebuggee.b, undefined);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 6);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ // Check that stepping worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, undefined);
+
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ gThreadClient.stepOver();
+
+ });
+ gThreadClient.stepOver();
+
+ });
+
+ gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+ "function f() {\n" + // line0 + 1
+ " this.a = 1;\n" + // line0 + 2
+ "}\n" + // line0 + 3
+ "debugger;\n" + // line0 + 4
+ "f();\n" + // line0 + 5
+ "let b = 2;\n"); // line0 + 6
+}
diff --git a/devtools/server/tests/unit/test_stepping-05.js b/devtools/server/tests/unit/test_stepping-05.js
new file mode 100644
index 000000000..2ab4570af
--- /dev/null
+++ b/devtools/server/tests/unit/test_stepping-05.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that stepping in the last statement of the last frame doesn't
+ * cause an unexpected pause, when another JS frame is pushed on the stack
+ * (bug 785689).
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_stepping_last();
+ });
+ });
+}
+
+function test_stepping_last()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 2);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ // Check that stepping worked.
+ do_check_eq(gDebuggee.a, undefined);
+ do_check_eq(gDebuggee.b, undefined);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ // Check that stepping worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, undefined);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ // When leaving a stack frame the line number doesn't change.
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 3);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ // Check that stepping worked.
+ do_check_eq(gDebuggee.a, 1);
+ do_check_eq(gDebuggee.b, 2);
+
+ gThreadClient.stepIn(function () {
+ test_next_pause();
+ });
+ });
+ gThreadClient.stepIn();
+ });
+ gThreadClient.stepIn();
+
+ });
+ gThreadClient.stepIn();
+
+ });
+
+ gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+ "debugger;\n" + // line0 + 1
+ "var a = 1;\n" + // line0 + 2
+ "var b = 2;\n"); // line0 + 3
+}
+
+function test_next_pause()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check the return value.
+ do_check_eq(aPacket.type, "paused");
+ // Before fixing bug 785689, the type was resumeLimit.
+ do_check_eq(aPacket.why.type, "debuggerStatement");
+
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+
+ gDebuggee.eval("debugger;");
+}
diff --git a/devtools/server/tests/unit/test_stepping-06.js b/devtools/server/tests/unit/test_stepping-06.js
new file mode 100644
index 000000000..49689f830
--- /dev/null
+++ b/devtools/server/tests/unit/test_stepping-06.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that stepping out of a function returns the right return value.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gCallback;
+
+function run_test()
+{
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+ do_test_pending();
+}
+
+function run_test_with_server(aServer, aCallback)
+{
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stack", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ // XXX: We have to do an executeSoon so that the error isn't caught and
+ // reported by DebuggerClient.requester (because we are using the local
+ // transport and share a stack) which causes the test to fail.
+ Services.tm.mainThread.dispatch({
+ run: test_simple_stepping
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ });
+ });
+}
+
+function test_simple_stepping()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check that the return value is 10.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 5);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ do_check_eq(aPacket.why.frameFinished.return, 10);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check that the return value is undefined.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 8);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ do_check_eq(aPacket.why.frameFinished.return.type, "undefined");
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Check that the exception was thrown.
+ do_check_eq(aPacket.type, "paused");
+ do_check_eq(aPacket.frame.where.line, gDebuggee.line0 + 11);
+ do_check_eq(aPacket.why.type, "resumeLimit");
+ do_check_eq(aPacket.why.frameFinished.throw, "ah");
+
+ gThreadClient.resume(function () {
+ gClient.close().then(gCallback);
+ });
+ });
+ gThreadClient.stepOut();
+ });
+ gThreadClient.resume();
+ });
+ gThreadClient.stepOut();
+ });
+ gThreadClient.resume();
+ });
+ gThreadClient.stepOut();
+
+ });
+
+ gDebuggee.eval("var line0 = Error().lineNumber;\n" +
+ "function f() {\n" + // line0 + 1
+ " debugger;\n" + // line0 + 2
+ " var a = 10;\n" + // line0 + 3
+ " return a;\n" + // line0 + 4
+ "}\n" + // line0 + 5
+ "function g() {\n" + // line0 + 6
+ " debugger;\n" + // line0 + 7
+ "}\n" + // line0 + 8
+ "function h() {\n" + // line0 + 9
+ " debugger;\n" + // line0 + 10
+ " throw 'ah';\n" + // line0 + 11
+ " return 2;\n" + // line0 + 12
+ "}\n" + // line0 + 13
+ "f();\n" + // line0 + 14
+ "g();\n" + // line0 + 15
+ "try { h() } catch (ex) { };\n"); // line0 + 16
+}
diff --git a/devtools/server/tests/unit/test_stepping-07.js b/devtools/server/tests/unit/test_stepping-07.js
new file mode 100644
index 000000000..9ad0d79ff
--- /dev/null
+++ b/devtools/server/tests/unit/test_stepping-07.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that stepping over an implicit return makes sense. Bug 1155966.
+ */
+
+var gDebuggee;
+var gClient;
+var gCallback;
+
+function run_test() {
+ do_test_pending();
+ run_test_with_server(DebuggerServer, function () {
+ run_test_with_server(WorkerDebuggerServer, do_test_finished);
+ });
+}
+
+function run_test_with_server(aServer, aCallback) {
+ gCallback = aCallback;
+ initTestDebuggerServer(aServer);
+ gDebuggee = addTestGlobal("test-stepping", aServer);
+ gClient = new DebuggerClient(aServer.connectPipe());
+ gClient.connect(testSteppingAndReturns);
+}
+
+const testSteppingAndReturns = Task.async(function* () {
+ const [attachResponse, tabClient, threadClient] = yield attachTestTabAndResume(gClient, "test-stepping");
+ ok(!attachResponse.error, "Should not get an error attaching");
+
+ dumpn("Evaluating test code and waiting for first debugger statement");
+ const dbgStmt1 = yield executeOnNextTickAndWaitForPause(evaluateTestCode, gClient);
+ equal(dbgStmt1.frame.where.line, 3,
+ "Should be at debugger statement on line 3");
+
+ dumpn("Testing stepping with implicit return");
+ const step1 = yield stepOver(gClient, threadClient);
+ equal(step1.frame.where.line, 4, "Should step to line 4");
+ const step2 = yield stepOver(gClient, threadClient);
+ equal(step2.frame.where.line, 7,
+ "Should step to line 7, the implicit return at the last line of the function");
+ // This assertion doesn't pass yet. You would need to do *another*
+ // step at the end of this function to get the frameFinished.
+ // See bug 923975.
+ //
+ // ok(step2.why.frameFinished, "This should be the implicit function return");
+
+ dumpn("Continuing and waiting for second debugger statement");
+ const dbgStmt2 = yield resumeAndWaitForPause(gClient, threadClient);
+ equal(dbgStmt2.frame.where.line, 12,
+ "Should be at debugger statement on line 3");
+
+ dumpn("Testing stepping with explicit return");
+ const step3 = yield stepOver(gClient, threadClient);
+ equal(step3.frame.where.line, 13, "Should step to line 13");
+ const step4 = yield stepOver(gClient, threadClient);
+ equal(step4.frame.where.line, 15, "Should step out of the function from line 15");
+ // This step is a bit funny, see bug 1013219 for details.
+ const step5 = yield stepOver(gClient, threadClient);
+ equal(step5.frame.where.line, 15, "Should step out of the function from line 15");
+ ok(step5.why.frameFinished, "This should be the explicit function return");
+
+ finishClient(gClient, gCallback);
+});
+
+function evaluateTestCode() {
+ Cu.evalInSandbox(
+ ` // 1
+ function implicitReturn() { // 2
+ debugger; // 3
+ if (this.someUndefinedProperty) { // 4
+ yikes(); // 5
+ } // 6
+ } // 7
+ // 8
+ var yes = true; // 9
+ function explicitReturn() { // 10
+ if (yes) { // 11
+ debugger; // 12
+ return 1; // 13
+ } // 14
+ } // 15
+ // 16
+ implicitReturn(); // 17
+ explicitReturn(); // 18
+ `, // 19
+ gDebuggee,
+ "1.8",
+ "test_stepping-07-test-code.js",
+ 1
+ );
+}
diff --git a/devtools/server/tests/unit/test_symbols-01.js b/devtools/server/tests/unit/test_symbols-01.js
new file mode 100644
index 000000000..8ea26086a
--- /dev/null
+++ b/devtools/server/tests/unit/test_symbols-01.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can represent ES6 Symbols over the RDP.
+ */
+
+const URL = "foo.js";
+
+function run_test() {
+ initTestDebuggerServer();
+ const debuggee = addTestGlobal("test-symbols");
+ const client = new DebuggerClient(DebuggerServer.connectPipe());
+
+ client.connect().then(function () {
+ attachTestTabAndResume(client, "test-symbols", function (response, tabClient, threadClient) {
+ add_task(testSymbols.bind(null, client, debuggee));
+ run_next_test();
+ });
+ });
+
+ do_test_pending();
+}
+
+function* testSymbols(client, debuggee) {
+ const evalCode = () => {
+ Components.utils.evalInSandbox(
+ "(" + function () {
+ var symbolWithName = Symbol("Chris");
+ var symbolWithoutName = Symbol();
+ var iteratorSymbol = Symbol.iterator;
+ debugger;
+ } + "())",
+ debuggee,
+ "1.8",
+ URL,
+ 1
+ );
+ };
+
+ const packet = yield executeOnNextTickAndWaitForPause(evalCode, client);
+ const {
+ symbolWithName,
+ symbolWithoutName,
+ iteratorSymbol
+ } = packet.frame.environment.bindings.variables;
+
+ equal(symbolWithName.value.type, "symbol");
+ equal(symbolWithName.value.name, "Chris");
+
+ equal(symbolWithoutName.value.type, "symbol");
+ ok(!("name" in symbolWithoutName.value));
+
+ equal(iteratorSymbol.value.type, "symbol");
+ equal(iteratorSymbol.value.name, "Symbol.iterator");
+
+ finishClient(client);
+}
diff --git a/devtools/server/tests/unit/test_symbols-02.js b/devtools/server/tests/unit/test_symbols-02.js
new file mode 100644
index 000000000..f80dc3322
--- /dev/null
+++ b/devtools/server/tests/unit/test_symbols-02.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we don't run debuggee code when getting symbol names.
+ */
+
+const URL = "foo.js";
+
+function run_test() {
+ initTestDebuggerServer();
+ const debuggee = addTestGlobal("test-symbols");
+ const client = new DebuggerClient(DebuggerServer.connectPipe());
+
+ client.connect().then(function () {
+ attachTestTabAndResume(client, "test-symbols", function (response, tabClient, threadClient) {
+ add_task(testSymbols.bind(null, client, debuggee));
+ run_next_test();
+ });
+ });
+
+ do_test_pending();
+}
+
+function* testSymbols(client, debuggee) {
+ const evalCode = () => {
+ Components.utils.evalInSandbox(
+ "(" + function () {
+ Symbol.prototype.toString = () => {
+ throw new Error("lololol");
+ };
+ var sym = Symbol("le troll");
+ debugger;
+ } + "())",
+ debuggee,
+ "1.8",
+ URL,
+ 1
+ );
+ };
+
+ const packet = yield executeOnNextTickAndWaitForPause(evalCode, client);
+ const { sym } = packet.frame.environment.bindings.variables;
+
+ equal(sym.value.type, "symbol");
+ equal(sym.value.name, "le troll");
+
+ finishClient(client);
+}
diff --git a/devtools/server/tests/unit/test_threadlifetime-01.js b/devtools/server/tests/unit/test_threadlifetime-01.js
new file mode 100644
index 000000000..1325a45fa
--- /dev/null
+++ b/devtools/server/tests/unit/test_threadlifetime-01.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that thread-lifetime grips last past a resume.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_thread_lifetime();
+ });
+ });
+ do_test_pending();
+}
+
+function test_thread_lifetime()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let pauseGrip = aPacket.frame.arguments[0];
+
+ // Create a thread-lifetime actor for this object.
+ gClient.request({ to: pauseGrip.actor, type: "threadGrip" }, function (aResponse) {
+ // Successful promotion won't return an error.
+ do_check_eq(aResponse.error, undefined);
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Verify that the promoted actor is returned again.
+ do_check_eq(pauseGrip.actor, aPacket.frame.arguments[0].actor);
+ // Now that we've resumed, should get unrecognizePacketType for the
+ // promoted grip.
+ gClient.request({ to: pauseGrip.actor, type: "bogusRequest"}, function (aResponse) {
+ do_check_eq(aResponse.error, "unrecognizedPacketType");
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+ gThreadClient.resume();
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(arg1) {
+ debugger;
+ debugger;
+ }
+ stopMe({obj: true});
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_threadlifetime-02.js b/devtools/server/tests/unit/test_threadlifetime-02.js
new file mode 100644
index 000000000..a7d21a7f9
--- /dev/null
+++ b/devtools/server/tests/unit/test_threadlifetime-02.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that thread-lifetime grips last past a resume.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_thread_lifetime();
+ });
+ });
+ do_test_pending();
+}
+
+function test_thread_lifetime()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let pauseGrip = aPacket.frame.arguments[0];
+
+ // Create a thread-lifetime actor for this object.
+ gClient.request({ to: pauseGrip.actor, type: "threadGrip" }, function (aResponse) {
+ // Successful promotion won't return an error.
+ do_check_eq(aResponse.error, undefined);
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Verify that the promoted actor is returned again.
+ do_check_eq(pauseGrip.actor, aPacket.frame.arguments[0].actor);
+ // Now that we've resumed, release the thread-lifetime grip.
+ gClient.release(pauseGrip.actor, function (aResponse) {
+ gClient.request({ to: pauseGrip.actor, type: "bogusRequest" }, function (aResponse) {
+ do_check_eq(aResponse.error, "noSuchActor");
+ gThreadClient.resume(function (aResponse) {
+ finishClient(gClient);
+ });
+ });
+ });
+ });
+ gThreadClient.resume();
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(arg1) {
+ debugger;
+ debugger;
+ }
+ stopMe({obj: true});
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_threadlifetime-03.js b/devtools/server/tests/unit/test_threadlifetime-03.js
new file mode 100644
index 000000000..22b707ff8
--- /dev/null
+++ b/devtools/server/tests/unit/test_threadlifetime-03.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that thread-lifetime grips last past a resume.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_thread_lifetime();
+ });
+ });
+ do_test_pending();
+}
+
+function test_thread_lifetime()
+{
+ // Get three thread-lifetime grips.
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let grips = [];
+
+ let handler = function (aResponse) {
+ if (aResponse.error) {
+ do_check_eq(aResponse.error, "");
+ finishClient(gClient);
+ }
+ grips.push(aResponse.from);
+ if (grips.length == 3) {
+ test_release_many(grips);
+ }
+ };
+ for (let i = 0; i < 3; i++) {
+ gClient.request({ to: aPacket.frame.arguments[i].actor, type: "threadGrip" },
+ handler);
+ }
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(arg1, arg2, arg3) {
+ debugger;
+ }
+ stopMe({obj: 1}, {obj: 2}, {obj: 3});
+ } + ")()");
+}
+
+function test_release_many(grips)
+{
+ // Release the first two grips, leave the third alive.
+
+ let release = [grips[0], grips[1]];
+
+ gThreadClient.releaseMany(release, function (aResponse) {
+ // First two actors should return a noSuchActor error, because
+ // they're gone now.
+ gClient.request({ to: grips[0], type: "bogusRequest" }, function (aResponse) {
+ do_check_eq(aResponse.error, "noSuchActor");
+ gClient.request({ to: grips[1], type: "bogusRequest" }, function (aResponse) {
+ do_check_eq(aResponse.error, "noSuchActor");
+
+ // Last actor should return unrecognizedPacketType, because it's still
+ // alive.
+ gClient.request({ to: grips[2], type: "bogusRequest" }, function (aResponse) {
+ do_check_eq(aResponse.error, "unrecognizedPacketType");
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/devtools/server/tests/unit/test_threadlifetime-04.js b/devtools/server/tests/unit/test_threadlifetime-04.js
new file mode 100644
index 000000000..aff8b525c
--- /dev/null
+++ b/devtools/server/tests/unit/test_threadlifetime-04.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that requesting a thread-lifetime actor twice for the same
+ * value returns the same actor.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_thread_lifetime();
+ });
+ });
+ do_test_pending();
+}
+
+function test_thread_lifetime()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let pauseGrip = aPacket.frame.arguments[0];
+
+ gClient.request({ to: pauseGrip.actor, type: "threadGrip" }, function (aResponse) {
+ // Successful promotion won't return an error.
+ do_check_eq(aResponse.error, undefined);
+
+ let threadGrip1 = aResponse.from;
+
+ gClient.request({ to: pauseGrip.actor, type: "threadGrip" }, function (aResponse) {
+ do_check_eq(threadGrip1, aResponse.from);
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(arg1) {
+ debugger;
+ }
+ stopMe({obj: true});
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_threadlifetime-05.js b/devtools/server/tests/unit/test_threadlifetime-05.js
new file mode 100644
index 000000000..6697947c1
--- /dev/null
+++ b/devtools/server/tests/unit/test_threadlifetime-05.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure that releasing a pause-lifetime actorin a releaseMany returns an
+ * error, but releases all the thread-lifetime actors.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gPauseGrip;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_thread_lifetime();
+ });
+ });
+ do_test_pending();
+}
+
+function arg_grips(aFrameArgs, aOnResponse) {
+ let grips = [];
+ let handler = function (aResponse) {
+ if (aResponse.error) {
+ grips.push(aResponse.error);
+ } else {
+ grips.push(aResponse.from);
+ }
+ if (grips.length == aFrameArgs.length) {
+ aOnResponse(grips);
+ }
+ };
+ for (let i = 0; i < aFrameArgs.length; i++) {
+ gClient.request({ to: aFrameArgs[i].actor, type: "threadGrip" },
+ handler);
+ }
+}
+
+function test_thread_lifetime()
+{
+ // Get two thread-lifetime grips.
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+
+ let frameArgs = [ aPacket.frame.arguments[0], aPacket.frame.arguments[1] ];
+ gPauseGrip = aPacket.frame.arguments[2];
+ arg_grips(frameArgs, function (aGrips) {
+ release_grips(frameArgs, aGrips);
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(arg1, arg2, arg3) {
+ debugger;
+ }
+ stopMe({obj: 1}, {obj: 2}, {obj: 3});
+ } + ")()");
+}
+
+
+function release_grips(aFrameArgs, aThreadGrips)
+{
+ // Release all actors with releaseMany...
+ let release = [aThreadGrips[0], aThreadGrips[1], gPauseGrip.actor];
+ gThreadClient.releaseMany(release, function (aResponse) {
+ do_check_eq(aResponse.error, "notReleasable");
+ // Now ask for thread grips again, they should not exist.
+ arg_grips(aFrameArgs, function (aNewGrips) {
+ for (let i = 0; i < aNewGrips.length; i++) {
+ do_check_eq(aNewGrips[i], "noSuchActor");
+ }
+ gThreadClient.resume(function () {
+ finishClient(gClient);
+ });
+ });
+ });
+}
diff --git a/devtools/server/tests/unit/test_threadlifetime-06.js b/devtools/server/tests/unit/test_threadlifetime-06.js
new file mode 100644
index 000000000..dba0156f9
--- /dev/null
+++ b/devtools/server/tests/unit/test_threadlifetime-06.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that promoting multiple pause-lifetime actors to thread-lifetime actors
+ * works as expected.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+
+function run_test()
+{
+ initTestDebuggerServer();
+ gDebuggee = addTestGlobal("test-grips");
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect().then(function () {
+ attachTestTabAndResume(gClient, "test-grips", function (aResponse, aTabClient, aThreadClient) {
+ gThreadClient = aThreadClient;
+ test_thread_lifetime();
+ });
+ });
+ do_test_pending();
+}
+
+function test_thread_lifetime()
+{
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ let actors = [];
+ let last;
+ for (let aGrip of aPacket.frame.arguments) {
+ actors.push(aGrip.actor);
+ last = aGrip.actor;
+ }
+
+ // Create thread-lifetime actors for these objects.
+ gThreadClient.threadGrips(actors, function (aResponse) {
+ // Successful promotion won't return an error.
+ do_check_eq(aResponse.error, undefined);
+
+ gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket) {
+ // Verify that the promoted actors are returned again.
+ actors.forEach(function (actor, i) {
+ do_check_eq(actor, aPacket.frame.arguments[i].actor);
+ });
+ // Now that we've resumed, release the thread-lifetime grips.
+ gThreadClient.releaseMany(actors, function (aResponse) {
+ // Successful release won't return an error.
+ do_check_eq(aResponse.error, undefined);
+
+ gClient.request({ to: last, type: "bogusRequest" }, function (aResponse) {
+ do_check_eq(aResponse.error, "noSuchActor");
+ gThreadClient.resume(function (aResponse) {
+ finishClient(gClient);
+ });
+ });
+ });
+ });
+ gThreadClient.resume();
+ });
+ });
+
+ gDebuggee.eval("(" + function () {
+ function stopMe(arg1, arg2, arg3) {
+ debugger;
+ debugger;
+ }
+ stopMe({obj: 1}, {obj: 2}, {obj: 3});
+ } + ")()");
+}
diff --git a/devtools/server/tests/unit/test_unsafeDereference.js b/devtools/server/tests/unit/test_unsafeDereference.js
new file mode 100644
index 000000000..d7afe645f
--- /dev/null
+++ b/devtools/server/tests/unit/test_unsafeDereference.js
@@ -0,0 +1,134 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+// Test Debugger.Object.prototype.unsafeDereference in the presence of
+// interesting cross-compartment wrappers.
+//
+// This is not really a debugger server test; it's more of a Debugger test.
+// But we need xpcshell and Components.utils.Sandbox to get
+// cross-compartment wrappers with interesting properties, and this is the
+// xpcshell test directory most closely related to the JS Debugger API.
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+// Add a method to Debugger.Object for fetching value properties
+// conveniently.
+Debugger.Object.prototype.getProperty = function (aName) {
+ let desc = this.getOwnPropertyDescriptor(aName);
+ if (!desc)
+ return undefined;
+ if (!desc.value) {
+ throw Error("Debugger.Object.prototype.getProperty: " +
+ "not a value property: " + aName);
+ }
+ return desc.value;
+};
+
+function run_test() {
+ // Create a low-privilege sandbox, and a chrome-privilege sandbox.
+ let contentBox = Components.utils.Sandbox("http://www.example.com");
+ let chromeBox = Components.utils.Sandbox(this);
+
+ // Create an objects in this compartment, and one in each sandbox. We'll
+ // refer to the objects as "mainObj", "contentObj", and "chromeObj", in
+ // variable and property names.
+ var mainObj = { name: "mainObj" };
+ Components.utils.evalInSandbox('var contentObj = { name: "contentObj" };',
+ contentBox);
+ Components.utils.evalInSandbox('var chromeObj = { name: "chromeObj" };',
+ chromeBox);
+
+ // Give each global a pointer to all the other globals' objects.
+ contentBox.mainObj = chromeBox.mainObj = mainObj;
+ var contentObj = chromeBox.contentObj = contentBox.contentObj;
+ var chromeObj = contentBox.chromeObj = chromeBox.chromeObj;
+
+ // First, a whole bunch of basic sanity checks, to ensure that JavaScript
+ // evaluated in various scopes really does see the world the way this
+ // test expects it to.
+
+ // The objects appear as global variables in the sandbox, and as
+ // the sandbox object's properties in chrome.
+ do_check_true(Components.utils.evalInSandbox("mainObj", contentBox)
+ === contentBox.mainObj);
+ do_check_true(Components.utils.evalInSandbox("contentObj", contentBox)
+ === contentBox.contentObj);
+ do_check_true(Components.utils.evalInSandbox("chromeObj", contentBox)
+ === contentBox.chromeObj);
+ do_check_true(Components.utils.evalInSandbox("mainObj", chromeBox)
+ === chromeBox.mainObj);
+ do_check_true(Components.utils.evalInSandbox("contentObj", chromeBox)
+ === chromeBox.contentObj);
+ do_check_true(Components.utils.evalInSandbox("chromeObj", chromeBox)
+ === chromeBox.chromeObj);
+
+ // We (the main global) can see properties of all objects in all globals.
+ do_check_true(contentBox.mainObj.name === "mainObj");
+ do_check_true(contentBox.contentObj.name === "contentObj");
+ do_check_true(contentBox.chromeObj.name === "chromeObj");
+
+ // chromeBox can see properties of all objects in all globals.
+ do_check_eq(Components.utils.evalInSandbox("mainObj.name", chromeBox),
+ "mainObj");
+ do_check_eq(Components.utils.evalInSandbox("contentObj.name", chromeBox),
+ "contentObj");
+ do_check_eq(Components.utils.evalInSandbox("chromeObj.name", chromeBox),
+ "chromeObj");
+
+ // contentBox can see properties of the content object, but not of either
+ // chrome object, because by default, content -> chrome wrappers hide all
+ // object properties.
+ do_check_eq(Components.utils.evalInSandbox("mainObj.name", contentBox),
+ undefined);
+ do_check_eq(Components.utils.evalInSandbox("contentObj.name", contentBox),
+ "contentObj");
+ do_check_eq(Components.utils.evalInSandbox("chromeObj.name", contentBox),
+ undefined);
+
+ // When viewing an object in compartment A from the vantage point of
+ // compartment B, Debugger should give the same results as debuggee code
+ // would.
+
+ // Create a debugger, debugging our two sandboxes.
+ let dbg = new Debugger;
+
+ // Create Debugger.Object instances referring to the two sandboxes, as
+ // seen from their own compartments.
+ let contentBoxDO = dbg.addDebuggee(contentBox);
+ let chromeBoxDO = dbg.addDebuggee(chromeBox);
+
+ // Use Debugger to view the objects from contentBox. We should get the
+ // same D.O instance from both getProperty and makeDebuggeeValue, and the
+ // same property visibility we checked for above.
+ let mainFromContentDO = contentBoxDO.getProperty("mainObj");
+ do_check_eq(mainFromContentDO, contentBoxDO.makeDebuggeeValue(mainObj));
+ do_check_eq(mainFromContentDO.getProperty("name"), undefined);
+ do_check_eq(mainFromContentDO.unsafeDereference(), mainObj);
+
+ let contentFromContentDO = contentBoxDO.getProperty("contentObj");
+ do_check_eq(contentFromContentDO, contentBoxDO.makeDebuggeeValue(contentObj));
+ do_check_eq(contentFromContentDO.getProperty("name"), "contentObj");
+ do_check_eq(contentFromContentDO.unsafeDereference(), contentObj);
+
+ let chromeFromContentDO = contentBoxDO.getProperty("chromeObj");
+ do_check_eq(chromeFromContentDO, contentBoxDO.makeDebuggeeValue(chromeObj));
+ do_check_eq(chromeFromContentDO.getProperty("name"), undefined);
+ do_check_eq(chromeFromContentDO.unsafeDereference(), chromeObj);
+
+ // Similarly, viewing from chromeBox.
+ let mainFromChromeDO = chromeBoxDO.getProperty("mainObj");
+ do_check_eq(mainFromChromeDO, chromeBoxDO.makeDebuggeeValue(mainObj));
+ do_check_eq(mainFromChromeDO.getProperty("name"), "mainObj");
+ do_check_eq(mainFromChromeDO.unsafeDereference(), mainObj);
+
+ let contentFromChromeDO = chromeBoxDO.getProperty("contentObj");
+ do_check_eq(contentFromChromeDO, chromeBoxDO.makeDebuggeeValue(contentObj));
+ do_check_eq(contentFromChromeDO.getProperty("name"), "contentObj");
+ do_check_eq(contentFromChromeDO.unsafeDereference(), contentObj);
+
+ let chromeFromChromeDO = chromeBoxDO.getProperty("chromeObj");
+ do_check_eq(chromeFromChromeDO, chromeBoxDO.makeDebuggeeValue(chromeObj));
+ do_check_eq(chromeFromChromeDO.getProperty("name"), "chromeObj");
+ do_check_eq(chromeFromChromeDO.unsafeDereference(), chromeObj);
+}
diff --git a/devtools/server/tests/unit/test_xpcshell_debugging.js b/devtools/server/tests/unit/test_xpcshell_debugging.js
new file mode 100644
index 000000000..7026a02b3
--- /dev/null
+++ b/devtools/server/tests/unit/test_xpcshell_debugging.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the xpcshell-test debug support. Ideally we should have this test
+// next to the xpcshell support code, but that's tricky...
+
+function run_test() {
+ let testFile = do_get_file("xpcshell_debugging_script.js");
+
+ // _setupDebuggerServer is from xpcshell-test's head.js
+ let testResumed = false;
+ let DebuggerServer = _setupDebuggerServer([testFile.path], () => testResumed = true);
+ let transport = DebuggerServer.connectPipe();
+ let client = new DebuggerClient(transport);
+ client.connect().then(() => {
+ // Even though we have no tabs, listTabs gives us the chromeDebugger.
+ client.getProcess().then(response => {
+ let actor = response.form.actor;
+ client.attachTab(actor, (response, tabClient) => {
+ tabClient.attachThread(null, (response, threadClient) => {
+ threadClient.addOneTimeListener("paused", (event, packet) => {
+ equal(packet.why.type, "breakpoint",
+ "yay - hit the breakpoint at the first line in our script");
+ // Resume again - next stop should be our "debugger" statement.
+ threadClient.addOneTimeListener("paused", (event, packet) => {
+ equal(packet.why.type, "debuggerStatement",
+ "yay - hit the 'debugger' statement in our script");
+ threadClient.resume(() => {
+ finishClient(client);
+ });
+ });
+ threadClient.resume();
+ });
+ // tell the thread to do the initial resume. This would cause the
+ // xpcshell test harness to resume and load the file under test.
+ threadClient.resume(response => {
+ // should have been told to resume the test itself.
+ ok(testResumed);
+ // Now load our test script.
+ load(testFile.path);
+ // and our "paused" listener above should get hit.
+ });
+ });
+ });
+ });
+ });
+ do_test_pending();
+}
diff --git a/devtools/server/tests/unit/testactors.js b/devtools/server/tests/unit/testactors.js
new file mode 100644
index 000000000..39564eeef
--- /dev/null
+++ b/devtools/server/tests/unit/testactors.js
@@ -0,0 +1,176 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
+const { RootActor } = require("devtools/server/actors/root");
+const { ThreadActor } = require("devtools/server/actors/script");
+const { DebuggerServer } = require("devtools/server/main");
+const { TabSources } = require("devtools/server/actors/utils/TabSources");
+const promise = require("promise");
+const makeDebugger = require("devtools/server/actors/utils/make-debugger");
+
+var gTestGlobals = [];
+DebuggerServer.addTestGlobal = function (aGlobal) {
+ gTestGlobals.push(aGlobal);
+};
+
+DebuggerServer.getTestGlobal = function (name) {
+ for (let g of gTestGlobals) {
+ if (g.__name == name) {
+ return g;
+ }
+ }
+
+ return null;
+};
+
+// A mock tab list, for use by tests. This simply presents each global in
+// gTestGlobals as a tab, and the list is fixed: it never calls its
+// onListChanged handler.
+//
+// As implemented now, we consult gTestGlobals when we're constructed, not
+// when we're iterated over, so tests have to add their globals before the
+// root actor is created.
+function TestTabList(aConnection) {
+ this.conn = aConnection;
+
+ // An array of actors for each global added with
+ // DebuggerServer.addTestGlobal.
+ this._tabActors = [];
+
+ // A pool mapping those actors' names to the actors.
+ this._tabActorPool = new ActorPool(aConnection);
+
+ for (let global of gTestGlobals) {
+ let actor = new TestTabActor(aConnection, global);
+ actor.selected = false;
+ this._tabActors.push(actor);
+ this._tabActorPool.addActor(actor);
+ }
+ if (this._tabActors.length > 0) {
+ this._tabActors[0].selected = true;
+ }
+
+ aConnection.addActorPool(this._tabActorPool);
+}
+
+TestTabList.prototype = {
+ constructor: TestTabList,
+ getList: function () {
+ return Promise.resolve([...this._tabActors]);
+ }
+};
+
+function createRootActor(aConnection)
+{
+ let root = new RootActor(aConnection, {
+ tabList: new TestTabList(aConnection),
+ globalActorFactories: DebuggerServer.globalActorFactories,
+ });
+
+ root.applicationType = "xpcshell-tests";
+ return root;
+}
+
+function TestTabActor(aConnection, aGlobal)
+{
+ this.conn = aConnection;
+ this._global = aGlobal;
+ this._global.wrappedJSObject = aGlobal;
+ this.threadActor = new ThreadActor(this, this._global);
+ this.conn.addActor(this.threadActor);
+ this._attached = false;
+ this._extraActors = {};
+ this.makeDebugger = makeDebugger.bind(null, {
+ findDebuggees: () => [this._global],
+ shouldAddNewGlobalAsDebuggee: g => g.hostAnnotations &&
+ g.hostAnnotations.type == "document" &&
+ g.hostAnnotations.element === this._global
+
+ });
+}
+
+TestTabActor.prototype = {
+ constructor: TestTabActor,
+ actorPrefix: "TestTabActor",
+
+ get window() {
+ return this._global;
+ },
+
+ get url() {
+ return this._global.__name;
+ },
+
+ get sources() {
+ if (!this._sources) {
+ this._sources = new TabSources(this.threadActor);
+ }
+ return this._sources;
+ },
+
+ form: function () {
+ let response = { actor: this.actorID, title: this._global.__name };
+
+ // Walk over tab actors added by extensions and add them to a new ActorPool.
+ let actorPool = new ActorPool(this.conn);
+ this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
+ if (!actorPool.isEmpty()) {
+ this._tabActorPool = actorPool;
+ this.conn.addActorPool(this._tabActorPool);
+ }
+
+ this._appendExtraActors(response);
+
+ return response;
+ },
+
+ onAttach: function (aRequest) {
+ this._attached = true;
+
+ let response = { type: "tabAttached", threadActor: this.threadActor.actorID };
+ this._appendExtraActors(response);
+
+ return response;
+ },
+
+ onDetach: function (aRequest) {
+ if (!this._attached) {
+ return { "error":"wrongState" };
+ }
+ return { type: "detached" };
+ },
+
+ onReload: function (aRequest) {
+ this.sources.reset({ sourceMaps: true });
+ this.threadActor.clearDebuggees();
+ this.threadActor.dbg.addDebuggees();
+ return {};
+ },
+
+ removeActorByName: function (aName) {
+ const actor = this._extraActors[aName];
+ if (this._tabActorPool) {
+ this._tabActorPool.removeActor(actor);
+ }
+ delete this._extraActors[aName];
+ },
+
+ /* Support for DebuggerServer.addTabActor. */
+ _createExtraActors: createExtraActors,
+ _appendExtraActors: appendExtraActors
+};
+
+TestTabActor.prototype.requestTypes = {
+ "attach": TestTabActor.prototype.onAttach,
+ "detach": TestTabActor.prototype.onDetach,
+ "reload": TestTabActor.prototype.onReload
+};
+
+exports.register = function (handle) {
+ handle.setRootActor(createRootActor);
+};
+
+exports.unregister = function (handle) {
+ handle.setRootActor(null);
+};
diff --git a/devtools/server/tests/unit/tracerlocations.js b/devtools/server/tests/unit/tracerlocations.js
new file mode 100644
index 000000000..aa4677c0f
--- /dev/null
+++ b/devtools/server/tests/unit/tracerlocations.js
@@ -0,0 +1,8 @@
+// For JS Tracer tests dealing with source locations.
+
+function foo(x) {
+ x += 6;
+ return "bar";
+}
+
+foo(42);
diff --git a/devtools/server/tests/unit/xpcshell.ini b/devtools/server/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..703aa48fb
--- /dev/null
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -0,0 +1,232 @@
+[DEFAULT]
+tags = devtools
+head = head_dbg.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+support-files =
+ babel_and_browserify_script_with_source_map.js
+ source-map-data/sourcemapped.coffee
+ source-map-data/sourcemapped.map
+ post_init_global_actors.js
+ post_init_tab_actors.js
+ pre_init_global_actors.js
+ pre_init_tab_actors.js
+ registertestactors-01.js
+ registertestactors-02.js
+ registertestactors-03.js
+ sourcemapped.js
+ testactors.js
+ hello-actor.js
+ setBreakpoint-on-column.js
+ setBreakpoint-on-column-in-gcd-script.js
+ setBreakpoint-on-column-with-no-offsets.js
+ setBreakpoint-on-column-with-no-offsets-at-end-of-line.js
+ setBreakpoint-on-column-with-no-offsets-in-gcd-script.js
+ setBreakpoint-on-line.js
+ setBreakpoint-on-line-in-gcd-script.js
+ setBreakpoint-on-line-with-multiple-offsets.js
+ setBreakpoint-on-line-with-multiple-statements.js
+ setBreakpoint-on-line-with-no-offsets.js
+ setBreakpoint-on-line-with-no-offsets-in-gcd-script.js
+ addons/web-extension/manifest.json
+ addons/web-extension-upgrade/manifest.json
+ addons/web-extension2/manifest.json
+
+[test_addon_reload.js]
+[test_addons_actor.js]
+[test_animation_name.js]
+[test_animation_type.js]
+[test_actor-registry-actor.js]
+[test_nesting-01.js]
+[test_nesting-02.js]
+[test_nesting-03.js]
+[test_forwardingprefix.js]
+[test_getyoungestframe.js]
+[test_nsjsinspector.js]
+[test_dbgactor.js]
+[test_dbgglobal.js]
+[test_dbgclient_debuggerstatement.js]
+[test_attach.js]
+[test_MemoryActor_saveHeapSnapshot_01.js]
+[test_MemoryActor_saveHeapSnapshot_02.js]
+[test_MemoryActor_saveHeapSnapshot_03.js]
+[test_reattach-thread.js]
+[test_blackboxing-01.js]
+[test_blackboxing-02.js]
+[test_blackboxing-03.js]
+[test_blackboxing-04.js]
+[test_blackboxing-05.js]
+[test_blackboxing-06.js]
+[test_blackboxing-07.js]
+[test_frameactor-01.js]
+[test_frameactor-02.js]
+[test_frameactor-03.js]
+[test_frameactor-04.js]
+[test_frameactor-05.js]
+[test_framearguments-01.js]
+[test_getRuleText.js]
+[test_getTextAtLineColumn.js]
+[test_pauselifetime-01.js]
+[test_pauselifetime-02.js]
+[test_pauselifetime-03.js]
+[test_pauselifetime-04.js]
+[test_threadlifetime-01.js]
+[test_threadlifetime-02.js]
+[test_threadlifetime-03.js]
+[test_threadlifetime-04.js]
+[test_threadlifetime-05.js]
+[test_threadlifetime-06.js]
+[test_functiongrips-01.js]
+[test_frameclient-01.js]
+[test_frameclient-02.js]
+[test_nativewrappers.js]
+[test_nodelistactor.js]
+[test_eval-01.js]
+[test_eval-02.js]
+[test_eval-03.js]
+[test_eval-04.js]
+[test_eval-05.js]
+[test_promises_actor_attach.js]
+[test_promises_actor_exist.js]
+[test_promises_actor_list_promises.js]
+[test_promises_actor_onnewpromise.js]
+[test_promises_actor_onpromisesettled.js]
+[test_promises_client_getdependentpromises.js]
+[test_promises_object_creationtimestamp.js]
+[test_promises_object_timetosettle-01.js]
+[test_promises_object_timetosettle-02.js]
+[test_protocol_abort.js]
+[test_protocol_async.js]
+[test_protocol_children.js]
+[test_protocol_formtype.js]
+[test_protocol_longstring.js]
+[test_protocol_simple.js]
+[test_protocol_stack.js]
+[test_protocol_unregister.js]
+[test_breakpoint-01.js]
+[test_register_actor.js]
+[test_breakpoint-02.js]
+[test_breakpoint-03.js]
+[test_breakpoint-04.js]
+[test_breakpoint-05.js]
+[test_breakpoint-06.js]
+[test_breakpoint-07.js]
+[test_breakpoint-08.js]
+[test_breakpoint-09.js]
+[test_breakpoint-10.js]
+[test_breakpoint-11.js]
+[test_breakpoint-12.js]
+[test_breakpoint-13.js]
+[test_breakpoint-14.js]
+[test_breakpoint-15.js]
+[test_breakpoint-16.js]
+[test_breakpoint-17.js]
+[test_breakpoint-18.js]
+[test_breakpoint-19.js]
+skip-if = true
+reason = bug 1104838
+[test_breakpoint-20.js]
+[test_breakpoint-21.js]
+[test_conditional_breakpoint-01.js]
+[test_conditional_breakpoint-02.js]
+[test_conditional_breakpoint-03.js]
+[test_eventlooplag_actor.js]
+skip-if = true
+reason = only ran on B2G
+[test_listsources-01.js]
+[test_listsources-02.js]
+[test_listsources-03.js]
+[test_listsources-04.js]
+[test_new_source-01.js]
+[test_sourcemaps-01.js]
+[test_sourcemaps-02.js]
+[test_sourcemaps-03.js]
+[test_sourcemaps-04.js]
+[test_sourcemaps-05.js]
+[test_sourcemaps-06.js]
+[test_sourcemaps-07.js]
+[test_sourcemaps-08.js]
+[test_sourcemaps-09.js]
+[test_sourcemaps-10.js]
+[test_sourcemaps-11.js]
+[test_sourcemaps-12.js]
+[test_sourcemaps-13.js]
+[test_sourcemaps-16.js]
+[test_sourcemaps-17.js]
+[test_objectgrips-01.js]
+[test_objectgrips-02.js]
+[test_objectgrips-03.js]
+[test_objectgrips-04.js]
+[test_objectgrips-05.js]
+[test_objectgrips-06.js]
+[test_objectgrips-07.js]
+[test_objectgrips-08.js]
+[test_objectgrips-09.js]
+[test_objectgrips-10.js]
+[test_objectgrips-11.js]
+[test_objectgrips-12.js]
+[test_objectgrips-13.js]
+[test_promise_state-01.js]
+[test_promise_state-02.js]
+[test_promise_state-03.js]
+[test_interrupt.js]
+[test_stepping-01.js]
+[test_stepping-02.js]
+[test_stepping-03.js]
+[test_stepping-04.js]
+[test_stepping-05.js]
+[test_stepping-06.js]
+[test_stepping-07.js]
+[test_framebindings-01.js]
+[test_framebindings-02.js]
+[test_framebindings-03.js]
+[test_framebindings-04.js]
+[test_framebindings-05.js]
+[test_framebindings-06.js]
+[test_framebindings-07.js]
+[test_pause_exceptions-01.js]
+[test_pause_exceptions-02.js]
+[test_longstringactor.js]
+[test_longstringgrips-01.js]
+[test_longstringgrips-02.js]
+[test_source-01.js]
+[test_breakpoint-actor-map.js]
+[test_profiler_activation-01.js]
+[test_profiler_activation-02.js]
+[test_profiler_close.js]
+[test_profiler_data.js]
+[test_profiler_events-01.js]
+[test_profiler_events-02.js]
+[test_profiler_getbufferinfo.js]
+[test_profiler_getfeatures.js]
+[test_profiler_getsharedlibraryinformation.js]
+[test_unsafeDereference.js]
+[test_add_actors.js]
+[test_ignore_caught_exceptions.js]
+[test_ignore_no_interface_exceptions.js]
+[test_requestTypes.js]
+reason = bug 937197
+[test_layout-reflows-observer.js]
+[test_protocolSpec.js]
+[test_registerClient.js]
+[test_client_request.js]
+[test_monitor_actor.js]
+[test_symbols-01.js]
+[test_symbols-02.js]
+[test_get-executable-lines.js]
+[test_get-executable-lines-source-map.js]
+[test_xpcshell_debugging.js]
+support-files = xpcshell_debugging_script.js
+[test_setBreakpoint-on-column.js]
+[test_setBreakpoint-on-column-in-gcd-script.js]
+[test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js]
+[test_setBreakpoint-on-line.js]
+[test_setBreakpoint-on-line-in-gcd-script.js]
+[test_setBreakpoint-on-line-with-multiple-offsets.js]
+[test_setBreakpoint-on-line-with-multiple-statements.js]
+[test_setBreakpoint-on-line-with-no-offsets.js]
+[test_setBreakpoint-on-line-with-no-offsets-in-gcd-script.js]
+[test_safe-getter.js]
+[test_client_close.js]
diff --git a/devtools/server/tests/unit/xpcshell_debugging_script.js b/devtools/server/tests/unit/xpcshell_debugging_script.js
new file mode 100644
index 000000000..de2870e96
--- /dev/null
+++ b/devtools/server/tests/unit/xpcshell_debugging_script.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This is a file that test_xpcshell_debugging.js debugs.
+
+// We should hit this dump as it is the first debuggable line
+dump("hello from the debugee!\n");
+
+debugger; // and why not check we hit this!?