summaryrefslogtreecommitdiffstats
path: root/devtools/server/tests
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/server/tests')
-rw-r--r--devtools/server/tests/browser/.eslintrc.js6
-rw-r--r--devtools/server/tests/browser/animation.html170
-rw-r--r--devtools/server/tests/browser/browser.ini97
-rw-r--r--devtools/server/tests/browser/browser_animation_emitMutations.js62
-rw-r--r--devtools/server/tests/browser/browser_animation_getFrames.js32
-rw-r--r--devtools/server/tests/browser/browser_animation_getMultipleStates.js55
-rw-r--r--devtools/server/tests/browser/browser_animation_getPlayers.js63
-rw-r--r--devtools/server/tests/browser/browser_animation_getProperties.js36
-rw-r--r--devtools/server/tests/browser/browser_animation_getStateAfterFinished.js55
-rw-r--r--devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js38
-rw-r--r--devtools/server/tests/browser/browser_animation_keepFinished.js54
-rw-r--r--devtools/server/tests/browser/browser_animation_playPauseIframe.js51
-rw-r--r--devtools/server/tests/browser/browser_animation_playPauseSeveral.js92
-rw-r--r--devtools/server/tests/browser/browser_animation_playerState.js123
-rw-r--r--devtools/server/tests/browser/browser_animation_reconstructState.js38
-rw-r--r--devtools/server/tests/browser/browser_animation_refreshTransitions.js77
-rw-r--r--devtools/server/tests/browser/browser_animation_setCurrentTime.js74
-rw-r--r--devtools/server/tests/browser/browser_animation_setPlaybackRate.js51
-rw-r--r--devtools/server/tests/browser/browser_animation_simple.js35
-rw-r--r--devtools/server/tests/browser/browser_animation_updatedState.js55
-rw-r--r--devtools/server/tests/browser/browser_canvasframe_helper_01.js90
-rw-r--r--devtools/server/tests/browser/browser_canvasframe_helper_02.js48
-rw-r--r--devtools/server/tests/browser/browser_canvasframe_helper_03.js102
-rw-r--r--devtools/server/tests/browser/browser_canvasframe_helper_04.js98
-rw-r--r--devtools/server/tests/browser/browser_canvasframe_helper_05.js112
-rw-r--r--devtools/server/tests/browser/browser_canvasframe_helper_06.js100
-rw-r--r--devtools/server/tests/browser/browser_directorscript_actors.js159
-rw-r--r--devtools/server/tests/browser/browser_directorscript_actors_error_events.js132
-rw-r--r--devtools/server/tests/browser/browser_directorscript_actors_exports.js87
-rw-r--r--devtools/server/tests/browser/browser_markers-cycle-collection.js33
-rw-r--r--devtools/server/tests/browser/browser_markers-docloading-01.js37
-rw-r--r--devtools/server/tests/browser/browser_markers-docloading-02.js35
-rw-r--r--devtools/server/tests/browser/browser_markers-docloading-03.js39
-rw-r--r--devtools/server/tests/browser/browser_markers-gc.js50
-rw-r--r--devtools/server/tests/browser/browser_markers-minor-gc.js32
-rw-r--r--devtools/server/tests/browser/browser_markers-parse-html.js29
-rw-r--r--devtools/server/tests/browser/browser_markers-styles.js34
-rw-r--r--devtools/server/tests/browser/browser_markers-timestamp.js43
-rw-r--r--devtools/server/tests/browser/browser_navigateEvents.js160
-rw-r--r--devtools/server/tests/browser/browser_perf-allocation-data.js38
-rw-r--r--devtools/server/tests/browser/browser_perf-profiler-01.js45
-rw-r--r--devtools/server/tests/browser/browser_perf-profiler-02.js46
-rw-r--r--devtools/server/tests/browser/browser_perf-profiler-03.js54
-rw-r--r--devtools/server/tests/browser/browser_perf-realtime-markers.js93
-rw-r--r--devtools/server/tests/browser/browser_perf-recording-actor-01.js80
-rw-r--r--devtools/server/tests/browser/browser_perf-recording-actor-02.js54
-rw-r--r--devtools/server/tests/browser/browser_perf-samples-01.js63
-rw-r--r--devtools/server/tests/browser/browser_perf-samples-02.js77
-rw-r--r--devtools/server/tests/browser/browser_register_actor.js76
-rw-r--r--devtools/server/tests/browser/browser_storage_dynamic_windows.js294
-rw-r--r--devtools/server/tests/browser/browser_storage_listings.js610
-rw-r--r--devtools/server/tests/browser/browser_storage_updates.js304
-rw-r--r--devtools/server/tests/browser/browser_stylesheets_getTextEmpty.js40
-rw-r--r--devtools/server/tests/browser/browser_stylesheets_nested-iframes.js38
-rw-r--r--devtools/server/tests/browser/browser_timeline.js63
-rw-r--r--devtools/server/tests/browser/browser_timeline_actors.js69
-rw-r--r--devtools/server/tests/browser/browser_timeline_iframes.js41
-rw-r--r--devtools/server/tests/browser/director-script-target.html15
-rw-r--r--devtools/server/tests/browser/doc_allocations.html21
-rw-r--r--devtools/server/tests/browser/doc_force_cc.html29
-rw-r--r--devtools/server/tests/browser/doc_force_gc.html27
-rw-r--r--devtools/server/tests/browser/doc_innerHTML.html21
-rw-r--r--devtools/server/tests/browser/doc_perf.html25
-rw-r--r--devtools/server/tests/browser/head.js203
-rw-r--r--devtools/server/tests/browser/navigate-first.html15
-rw-r--r--devtools/server/tests/browser/navigate-second.html9
-rw-r--r--devtools/server/tests/browser/storage-dynamic-windows.html117
-rw-r--r--devtools/server/tests/browser/storage-helpers.js85
-rw-r--r--devtools/server/tests/browser/storage-listings.html123
-rw-r--r--devtools/server/tests/browser/storage-secured-iframe.html94
-rw-r--r--devtools/server/tests/browser/storage-unsecured-iframe.html26
-rw-r--r--devtools/server/tests/browser/storage-updates.html47
-rw-r--r--devtools/server/tests/browser/stylesheets-nested-iframes.html25
-rw-r--r--devtools/server/tests/browser/timeline-iframe-child.html19
-rw-r--r--devtools/server/tests/browser/timeline-iframe-parent.html11
-rw-r--r--devtools/server/tests/mochitest/.eslintrc.js6
-rw-r--r--devtools/server/tests/mochitest/Debugger.Source.prototype.element-2.js1
-rw-r--r--devtools/server/tests/mochitest/Debugger.Source.prototype.element.html17
-rw-r--r--devtools/server/tests/mochitest/Debugger.Source.prototype.element.js1
-rw-r--r--devtools/server/tests/mochitest/animation-data.html120
-rw-r--r--devtools/server/tests/mochitest/chrome.ini103
-rw-r--r--devtools/server/tests/mochitest/director-helpers.js44
-rw-r--r--devtools/server/tests/mochitest/hello-actor.js26
-rw-r--r--devtools/server/tests/mochitest/inspector-delay-image-response.sjs42
-rw-r--r--devtools/server/tests/mochitest/inspector-eyedropper.html18
-rw-r--r--devtools/server/tests/mochitest/inspector-helpers.js310
-rw-r--r--devtools/server/tests/mochitest/inspector-search-data.html52
-rw-r--r--devtools/server/tests/mochitest/inspector-styles-data.css3
-rw-r--r--devtools/server/tests/mochitest/inspector-styles-data.html81
-rw-r--r--devtools/server/tests/mochitest/inspector-traversal-data.html90
-rw-r--r--devtools/server/tests/mochitest/inspector_css-properties.html10
-rw-r--r--devtools/server/tests/mochitest/inspector_getImageData.html21
-rw-r--r--devtools/server/tests/mochitest/large-image.jpgbin0 -> 793541 bytes
-rw-r--r--devtools/server/tests/mochitest/memory-helpers.js52
-rw-r--r--devtools/server/tests/mochitest/nonchrome_unsafeDereference.html8
-rw-r--r--devtools/server/tests/mochitest/setup-in-child.js20
-rw-r--r--devtools/server/tests/mochitest/setup-in-parent.js10
-rw-r--r--devtools/server/tests/mochitest/small-image.gifbin0 -> 510655 bytes
-rw-r--r--devtools/server/tests/mochitest/test_Debugger.Script.prototype.global.html48
-rw-r--r--devtools/server/tests/mochitest/test_Debugger.Source.prototype.element.html182
-rw-r--r--devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionScript.html97
-rw-r--r--devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionType.html181
-rw-r--r--devtools/server/tests/mochitest/test_animation_actor-lifetime.html91
-rw-r--r--devtools/server/tests/mochitest/test_connectToChild.html134
-rw-r--r--devtools/server/tests/mochitest/test_connection-manager.html119
-rw-r--r--devtools/server/tests/mochitest/test_css-logic-media-queries.html62
-rw-r--r--devtools/server/tests/mochitest/test_css-logic-specificity.html84
-rw-r--r--devtools/server/tests/mochitest/test_css-logic.html167
-rw-r--r--devtools/server/tests/mochitest/test_css-properties_01.html121
-rw-r--r--devtools/server/tests/mochitest/test_css-properties_02.html86
-rw-r--r--devtools/server/tests/mochitest/test_device.html99
-rw-r--r--devtools/server/tests/mochitest/test_director.html114
-rw-r--r--devtools/server/tests/mochitest/test_director_connectToChild.html98
-rw-r--r--devtools/server/tests/mochitest/test_executeInGlobal-outerized_this.html69
-rw-r--r--devtools/server/tests/mochitest/test_framerate_01.html141
-rw-r--r--devtools/server/tests/mochitest/test_framerate_02.html113
-rw-r--r--devtools/server/tests/mochitest/test_framerate_03.html82
-rw-r--r--devtools/server/tests/mochitest/test_framerate_04.html72
-rw-r--r--devtools/server/tests/mochitest/test_framerate_05.html77
-rw-r--r--devtools/server/tests/mochitest/test_framerate_06.html82
-rw-r--r--devtools/server/tests/mochitest/test_getProcess.html120
-rw-r--r--devtools/server/tests/mochitest/test_inspector-anonymous.html201
-rw-r--r--devtools/server/tests/mochitest/test_inspector-changeattrs.html99
-rw-r--r--devtools/server/tests/mochitest/test_inspector-changevalue.html82
-rw-r--r--devtools/server/tests/mochitest/test_inspector-dead-nodes.html386
-rw-r--r--devtools/server/tests/mochitest/test_inspector-duplicate-node.html75
-rw-r--r--devtools/server/tests/mochitest/test_inspector-hide.html76
-rw-r--r--devtools/server/tests/mochitest/test_inspector-insert.html119
-rw-r--r--devtools/server/tests/mochitest/test_inspector-mutations-attr.html167
-rw-r--r--devtools/server/tests/mochitest/test_inspector-mutations-childlist.html310
-rw-r--r--devtools/server/tests/mochitest/test_inspector-mutations-events.html183
-rw-r--r--devtools/server/tests/mochitest/test_inspector-mutations-frameload.html214
-rw-r--r--devtools/server/tests/mochitest/test_inspector-mutations-value.html168
-rw-r--r--devtools/server/tests/mochitest/test_inspector-pick-color.html101
-rw-r--r--devtools/server/tests/mochitest/test_inspector-pseudoclass-lock.html174
-rw-r--r--devtools/server/tests/mochitest/test_inspector-release.html102
-rw-r--r--devtools/server/tests/mochitest/test_inspector-reload.html80
-rw-r--r--devtools/server/tests/mochitest/test_inspector-remove.html117
-rw-r--r--devtools/server/tests/mochitest/test_inspector-resize.html77
-rw-r--r--devtools/server/tests/mochitest/test_inspector-resolve-url.html88
-rw-r--r--devtools/server/tests/mochitest/test_inspector-retain.html180
-rw-r--r--devtools/server/tests/mochitest/test_inspector-scroll-into-view.html87
-rw-r--r--devtools/server/tests/mochitest/test_inspector-search-front.html217
-rw-r--r--devtools/server/tests/mochitest/test_inspector-search.html296
-rw-r--r--devtools/server/tests/mochitest/test_inspector-traversal.html354
-rw-r--r--devtools/server/tests/mochitest/test_inspector_getImageData-wait-for-load.html136
-rw-r--r--devtools/server/tests/mochitest/test_inspector_getImageData.html166
-rw-r--r--devtools/server/tests/mochitest/test_inspector_getImageDataFromURL.html114
-rw-r--r--devtools/server/tests/mochitest/test_inspector_getNodeFromActor.html88
-rw-r--r--devtools/server/tests/mochitest/test_makeGlobalObjectReference.html86
-rw-r--r--devtools/server/tests/mochitest/test_memory.html37
-rw-r--r--devtools/server/tests/mochitest/test_memory_allocations_01.html98
-rw-r--r--devtools/server/tests/mochitest/test_memory_allocations_02.html76
-rw-r--r--devtools/server/tests/mochitest/test_memory_allocations_03.html78
-rw-r--r--devtools/server/tests/mochitest/test_memory_allocations_04.html60
-rw-r--r--devtools/server/tests/mochitest/test_memory_allocations_05.html87
-rw-r--r--devtools/server/tests/mochitest/test_memory_allocations_06.html49
-rw-r--r--devtools/server/tests/mochitest/test_memory_allocations_07.html55
-rw-r--r--devtools/server/tests/mochitest/test_memory_attach_01.html31
-rw-r--r--devtools/server/tests/mochitest/test_memory_attach_02.html49
-rw-r--r--devtools/server/tests/mochitest/test_memory_census.html33
-rw-r--r--devtools/server/tests/mochitest/test_memory_gc_01.html46
-rw-r--r--devtools/server/tests/mochitest/test_memory_gc_events.html42
-rw-r--r--devtools/server/tests/mochitest/test_preference.html115
-rw-r--r--devtools/server/tests/mochitest/test_settings.html130
-rw-r--r--devtools/server/tests/mochitest/test_setupInParentChild.html110
-rw-r--r--devtools/server/tests/mochitest/test_styles-applied.html145
-rw-r--r--devtools/server/tests/mochitest/test_styles-computed.html139
-rw-r--r--devtools/server/tests/mochitest/test_styles-layout.html116
-rw-r--r--devtools/server/tests/mochitest/test_styles-matched.html98
-rw-r--r--devtools/server/tests/mochitest/test_styles-modify.html116
-rw-r--r--devtools/server/tests/mochitest/test_styles-svg.html70
-rw-r--r--devtools/server/tests/mochitest/test_unsafeDereference.html52
-rw-r--r--devtools/server/tests/mochitest/test_websocket-server.html82
-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
397 files changed, 32362 insertions, 0 deletions
diff --git a/devtools/server/tests/browser/.eslintrc.js b/devtools/server/tests/browser/.eslintrc.js
new file mode 100644
index 000000000..c5b919ce3
--- /dev/null
+++ b/devtools/server/tests/browser/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools mochitest eslintrc config.
+ "extends": "../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/server/tests/browser/animation.html b/devtools/server/tests/browser/animation.html
new file mode 100644
index 000000000..d10a9873d
--- /dev/null
+++ b/devtools/server/tests/browser/animation.html
@@ -0,0 +1,170 @@
+<!DOCTYPE html>
+<style>
+ .not-animated {
+ display: inline-block;
+
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ background: #eee;
+ }
+
+ .simple-animation {
+ display: inline-block;
+
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ background: red;
+
+ animation: move 200s infinite;
+ }
+
+ .multiple-animations {
+ display: inline-block;
+
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ background: #eee;
+
+ animation: move 200s infinite , glow 100s 5;
+ animation-timing-function: ease-out;
+ animation-direction: reverse;
+ animation-fill-mode: both;
+ }
+
+ .transition {
+ display: inline-block;
+
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ background: #f06;
+
+ transition: width 500s ease-out;
+ }
+ .transition.get-round {
+ width: 200px;
+ }
+
+ .long-animation {
+ display: inline-block;
+
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ background: gold;
+
+ animation: move 100s;
+ }
+
+ .short-animation {
+ display: inline-block;
+
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ background: purple;
+
+ animation: move 1s;
+ }
+
+ .delayed-animation {
+ display: inline-block;
+
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ background: rebeccapurple;
+
+ animation: move 200s 5s infinite;
+ }
+
+ .delayed-transition {
+ display: inline-block;
+
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ background: black;
+
+ transition: width 500s 3s;
+ }
+ .delayed-transition.get-round {
+ width: 200px;
+ }
+
+ .delayed-multiple-animations {
+ display: inline-block;
+
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ background: green;
+
+ animation: move .5s 1s 10, glow 1s .75s 30;
+ }
+
+ .multiple-animations-2 {
+ display: inline-block;
+
+ width: 50px;
+ height: 50px;
+ border-radius: 50%;
+ background: blue;
+
+ animation: move .5s, glow 100s 2s infinite, grow 300s 1s 100;
+ }
+
+ .all-transitions {
+ position: absolute;
+ top: 0;
+ right: 0;
+ width: 50px;
+ height: 50px;
+ background: blue;
+ transition: all .2s;
+ }
+ .all-transitions.expand {
+ width: 200px;
+ height: 100px;
+ }
+
+ @keyframes move {
+ 100% {
+ transform: translateY(100px);
+ }
+ }
+
+ @keyframes glow {
+ 100% {
+ background: yellow;
+ }
+ }
+
+ @keyframes grow {
+ 100% {
+ width: 100px;
+ }
+ }
+</style>
+<div class="not-animated"></div>
+<div class="simple-animation"></div>
+<div class="multiple-animations"></div>
+<div class="transition"></div>
+<div class="long-animation"></div>
+<div class="short-animation"></div>
+<div class="delayed-animation"></div>
+<div class="delayed-transition"></div>
+<div class="delayed-multiple-animations"></div>
+<div class="multiple-animations-2"></div>
+<div class="all-transitions"></div>
+<script type="text/javascript">
+ // Get the transitions started when the page loads
+ var players;
+ addEventListener("load", function() {
+ document.querySelector(".transition").classList.add("get-round");
+ document.querySelector(".delayed-transition").classList.add("get-round");
+ });
+</script>
diff --git a/devtools/server/tests/browser/browser.ini b/devtools/server/tests/browser/browser.ini
new file mode 100644
index 000000000..c05933230
--- /dev/null
+++ b/devtools/server/tests/browser/browser.ini
@@ -0,0 +1,97 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ head.js
+ animation.html
+ doc_allocations.html
+ doc_force_cc.html
+ doc_force_gc.html
+ doc_innerHTML.html
+ doc_perf.html
+ navigate-first.html
+ navigate-second.html
+ storage-dynamic-windows.html
+ storage-listings.html
+ storage-unsecured-iframe.html
+ storage-updates.html
+ storage-secured-iframe.html
+ stylesheets-nested-iframes.html
+ timeline-iframe-child.html
+ timeline-iframe-parent.html
+ director-script-target.html
+ storage-helpers.js
+ !/devtools/server/tests/mochitest/hello-actor.js
+
+[browser_animation_emitMutations.js]
+[browser_animation_getFrames.js]
+[browser_animation_getProperties.js]
+[browser_animation_getMultipleStates.js]
+[browser_animation_getPlayers.js]
+[browser_animation_getStateAfterFinished.js]
+[browser_animation_getSubTreeAnimations.js]
+[browser_animation_keepFinished.js]
+[browser_animation_playerState.js]
+[browser_animation_playPauseIframe.js]
+[browser_animation_playPauseSeveral.js]
+[browser_animation_reconstructState.js]
+[browser_animation_refreshTransitions.js]
+[browser_animation_setCurrentTime.js]
+[browser_animation_setPlaybackRate.js]
+[browser_animation_simple.js]
+[browser_animation_updatedState.js]
+skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+[browser_canvasframe_helper_01.js]
+skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+[browser_canvasframe_helper_02.js]
+[browser_canvasframe_helper_03.js]
+skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+[browser_canvasframe_helper_04.js]
+skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+[browser_canvasframe_helper_05.js]
+skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+[browser_canvasframe_helper_06.js]
+skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+[browser_markers-cycle-collection.js]
+[browser_markers-docloading-01.js]
+[browser_markers-docloading-02.js]
+[browser_markers-docloading-03.js]
+[browser_markers-gc.js]
+[browser_markers-minor-gc.js]
+[browser_markers-parse-html.js]
+[browser_markers-styles.js]
+[browser_markers-timestamp.js]
+[browser_navigateEvents.js]
+skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+[browser_perf-allocation-data.js]
+skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+[browser_perf-profiler-01.js]
+[browser_perf-profiler-02.js]
+skip-if = true # Needs to be updated for async actor destruction
+[browser_perf-profiler-03.js]
+skip-if = true # Needs to be updated for async actor destruction
+[browser_perf-realtime-markers.js]
+[browser_perf-recording-actor-01.js]
+skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+[browser_perf-recording-actor-02.js]
+skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+[browser_perf-samples-01.js]
+[browser_perf-samples-02.js]
+#[browser_perf-front-profiler-01.js] bug 1077464
+#[browser_perf-front-profiler-05.js] bug 1077464
+#[browser_perf-front-profiler-06.js]
+[browser_storage_dynamic_windows.js]
+[browser_storage_listings.js]
+[browser_storage_updates.js]
+[browser_stylesheets_getTextEmpty.js]
+[browser_stylesheets_nested-iframes.js]
+[browser_timeline.js]
+[browser_timeline_actors.js]
+[browser_timeline_iframes.js]
+[browser_directorscript_actors_exports.js]
+skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+[browser_directorscript_actors_error_events.js]
+skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+[browser_directorscript_actors.js]
+skip-if = e10s # Bug 1183605 - devtools/server/tests/browser/ tests are still disabled in E10S
+[browser_register_actor.js]
diff --git a/devtools/server/tests/browser/browser_animation_emitMutations.js b/devtools/server/tests/browser/browser_animation_emitMutations.js
new file mode 100644
index 000000000..4783a8209
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_emitMutations.js
@@ -0,0 +1,62 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the AnimationsActor emits events about changed animations on a
+// node after getAnimationPlayersForNode was called on that node.
+
+add_task(function* () {
+ let {client, walker, animations} =
+ yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+ info("Retrieve a non-animated node");
+ let node = yield walker.querySelector(walker.rootNode, ".not-animated");
+
+ info("Retrieve the animation player for the node");
+ let players = yield animations.getAnimationPlayersForNode(node);
+ is(players.length, 0, "The node has no animation players");
+
+ info("Listen for new animations");
+ let onMutations = once(animations, "mutations");
+
+ info("Add a couple of animation on the node");
+ yield node.modifyAttributes([
+ {attributeName: "class", newValue: "multiple-animations"}
+ ]);
+ let changes = yield onMutations;
+
+ ok(true, "The mutations event was emitted");
+ is(changes.length, 2, "There are 2 changes in the mutation event");
+ ok(changes.every(({type}) => type === "added"), "Both changes are additions");
+
+ let names = changes.map(c => c.player.initialState.name).sort();
+ is(names[0], "glow", "The animation 'glow' was added");
+ is(names[1], "move", "The animation 'move' was added");
+
+ info("Store the 2 new players for comparing later");
+ let p1 = changes[0].player;
+ let p2 = changes[1].player;
+
+ info("Listen for removed animations");
+ onMutations = once(animations, "mutations");
+
+ info("Remove the animation css class on the node");
+ yield node.modifyAttributes([
+ {attributeName: "class", newValue: "not-animated"}
+ ]);
+
+ changes = yield onMutations;
+
+ ok(true, "The mutations event was emitted");
+ is(changes.length, 2, "There are 2 changes in the mutation event");
+ ok(changes.every(({type}) => type === "removed"), "Both are removals");
+ ok(changes[0].player === p1 || changes[0].player === p2,
+ "The first removed player was one of the previously added players");
+ ok(changes[1].player === p1 || changes[1].player === p2,
+ "The second removed player was one of the previously added players");
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_animation_getFrames.js b/devtools/server/tests/browser/browser_animation_getFrames.js
new file mode 100644
index 000000000..25ccfae3b
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_getFrames.js
@@ -0,0 +1,32 @@
+/* vim: set 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";
+
+// Check that the AnimationPlayerActor exposes a getFrames method that returns
+// the list of keyframes in the animation.
+
+const URL = MAIN_DOMAIN + "animation.html";
+
+add_task(function* () {
+ let {client, walker, animations} =
+ yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+ info("Get the test node and its animation front");
+ let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+ let [player] = yield animations.getAnimationPlayersForNode(node);
+
+ ok(player.getFrames, "The front has the getFrames method");
+
+ let frames = yield player.getFrames();
+ is(frames.length, 2, "The correct number of keyframes was retrieved");
+ ok(frames[0].transform, "Frame 0 has the transform property");
+ ok(frames[1].transform, "Frame 1 has the transform property");
+ // Note that we don't really test the content of the frame object here on
+ // purpose. This object comes straight out of the web animations API
+ // unmodified.
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_animation_getMultipleStates.js b/devtools/server/tests/browser/browser_animation_getMultipleStates.js
new file mode 100644
index 000000000..4436695b0
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_getMultipleStates.js
@@ -0,0 +1,55 @@
+/* vim: set 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";
+
+add_task(function* setup() {
+ yield SpecialPowers.pushPrefEnv({
+ set: [["dom.ipc.processCount", 1]]
+ });
+});
+
+// Check that the duration, iterationCount and delay are retrieved correctly for
+// multiple animations.
+
+add_task(function* () {
+ let {client, walker, animations} =
+ yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+ yield playerHasAnInitialState(walker, animations);
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
+
+function* playerHasAnInitialState(walker, animations) {
+ let state = yield getAnimationStateForNode(walker, animations,
+ ".delayed-multiple-animations", 0);
+
+ ok(state.duration, 50000,
+ "The duration of the first animation is correct");
+ ok(state.iterationCount, 10,
+ "The iterationCount of the first animation is correct");
+ ok(state.delay, 1000,
+ "The delay of the first animation is correct");
+
+ state = yield getAnimationStateForNode(walker, animations,
+ ".delayed-multiple-animations", 1);
+
+ ok(state.duration, 100000,
+ "The duration of the secon animation is correct");
+ ok(state.iterationCount, 30,
+ "The iterationCount of the secon animation is correct");
+ ok(state.delay, 750,
+ "The delay of the secon animation is correct");
+}
+
+function* getAnimationStateForNode(walker, animations, selector, playerIndex) {
+ let node = yield walker.querySelector(walker.rootNode, selector);
+ let players = yield animations.getAnimationPlayersForNode(node);
+ let player = players[playerIndex];
+ yield player.ready();
+ let state = yield player.getCurrentState();
+ return state;
+}
diff --git a/devtools/server/tests/browser/browser_animation_getPlayers.js b/devtools/server/tests/browser/browser_animation_getPlayers.js
new file mode 100644
index 000000000..a99a4dc4e
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_getPlayers.js
@@ -0,0 +1,63 @@
+/* vim: set 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";
+
+// Check the output of getAnimationPlayersForNode
+
+add_task(function* () {
+ let {client, walker, animations} =
+ yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+ yield theRightNumberOfPlayersIsReturned(walker, animations);
+ yield playersCanBePausedAndResumed(walker, animations);
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
+
+function* theRightNumberOfPlayersIsReturned(walker, animations) {
+ let node = yield walker.querySelector(walker.rootNode, ".not-animated");
+ let players = yield animations.getAnimationPlayersForNode(node);
+ is(players.length, 0,
+ "0 players were returned for the unanimated node");
+
+ node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+ players = yield animations.getAnimationPlayersForNode(node);
+ is(players.length, 1,
+ "One animation player was returned");
+
+ node = yield walker.querySelector(walker.rootNode, ".multiple-animations");
+ players = yield animations.getAnimationPlayersForNode(node);
+ is(players.length, 2,
+ "Two animation players were returned");
+
+ node = yield walker.querySelector(walker.rootNode, ".transition");
+ players = yield animations.getAnimationPlayersForNode(node);
+ is(players.length, 1,
+ "One animation player was returned for the transitioned node");
+}
+
+function* playersCanBePausedAndResumed(walker, animations) {
+ let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+ let [player] = yield animations.getAnimationPlayersForNode(node);
+ yield player.ready();
+
+ ok(player.initialState,
+ "The player has an initialState");
+ ok(player.getCurrentState,
+ "The player has the getCurrentState method");
+ is(player.initialState.playState, "running",
+ "The animation is currently running");
+
+ yield player.pause();
+ let state = yield player.getCurrentState();
+ is(state.playState, "paused",
+ "The animation is now paused");
+
+ yield player.play();
+ state = yield player.getCurrentState();
+ is(state.playState, "running",
+ "The animation is now running again");
+}
diff --git a/devtools/server/tests/browser/browser_animation_getProperties.js b/devtools/server/tests/browser/browser_animation_getProperties.js
new file mode 100644
index 000000000..e95db544c
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_getProperties.js
@@ -0,0 +1,36 @@
+/* vim: set 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";
+
+// Check that the AnimationPlayerActor exposes a getProperties method that
+// returns the list of animated properties in the animation.
+
+const URL = MAIN_DOMAIN + "animation.html";
+
+add_task(function* () {
+ let {client, walker, animations} = yield initAnimationsFrontForUrl(URL);
+
+ info("Get the test node and its animation front");
+ let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+ let [player] = yield animations.getAnimationPlayersForNode(node);
+
+ ok(player.getProperties, "The front has the getProperties method");
+
+ let properties = yield player.getProperties();
+ is(properties.length, 1, "The correct number of properties was retrieved");
+
+ let propertyObject = properties[0];
+ is(propertyObject.name, "transform", "Property 0 is transform");
+
+ is(propertyObject.values.length, 2,
+ "The correct number of property values was retrieved");
+
+ // Note that we don't really test the content of the frame object here on
+ // purpose. This object comes straight out of the web animations API
+ // unmodified.
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js b/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js
new file mode 100644
index 000000000..dd33237c1
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_getStateAfterFinished.js
@@ -0,0 +1,55 @@
+/* vim: set 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";
+
+// Check that the right duration/iterationCount/delay are retrieved even when
+// the node has multiple animations and one of them already ended before getting
+// the player objects.
+// See devtools/server/actors/animation.js |getPlayerIndex| for more
+// information.
+
+add_task(function* () {
+ let {client, walker, animations} =
+ yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+ info("Retrieve a non animated node");
+ let node = yield walker.querySelector(walker.rootNode, ".not-animated");
+
+ info("Apply the multiple-animations-2 class to start the animations");
+ yield node.modifyAttributes([
+ {attributeName: "class", newValue: "multiple-animations-2"}
+ ]);
+
+ info("Get the list of players, by the time this executes, the first, " +
+ "short, animation should have ended.");
+ let players = yield animations.getAnimationPlayersForNode(node);
+ if (players.length === 3) {
+ info("The short animation hasn't ended yet, wait for a bit.");
+ // The animation lasts for 500ms, so 1000ms should do it.
+ yield new Promise(resolve => setTimeout(resolve, 1000));
+
+ info("And get the list again");
+ players = yield animations.getAnimationPlayersForNode(node);
+ }
+
+ is(players.length, 2, "2 animations remain on the node");
+
+ is(players[0].state.duration, 100000,
+ "The duration of the first animation is correct");
+ is(players[0].state.delay, 2000,
+ "The delay of the first animation is correct");
+ is(players[0].state.iterationCount, null,
+ "The iterationCount of the first animation is correct");
+
+ is(players[1].state.duration, 300000,
+ "The duration of the second animation is correct");
+ is(players[1].state.delay, 1000,
+ "The delay of the second animation is correct");
+ is(players[1].state.iterationCount, 100,
+ "The iterationCount of the second animation is correct");
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js b/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js
new file mode 100644
index 000000000..50782d6de
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_getSubTreeAnimations.js
@@ -0,0 +1,38 @@
+/* vim: set 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";
+
+// Check that the AnimationsActor can retrieve all animations inside a node's
+// subtree (but not going into iframes).
+
+const URL = MAIN_DOMAIN + "animation.html";
+
+add_task(function* () {
+ info("Creating a test document with 2 iframes containing animated nodes");
+
+ let {client, walker, animations} = yield initAnimationsFrontForUrl(
+ "data:text/html;charset=utf-8," +
+ "<iframe id='iframe' src='" + URL + "'></iframe>");
+
+ info("Try retrieving all animations from the root doc's <body> node");
+ let rootBody = yield walker.querySelector(walker.rootNode, "body");
+ let players = yield animations.getAnimationPlayersForNode(rootBody);
+ is(players.length, 0, "The node has no animation players");
+
+ info("Retrieve all animations from the iframe's <body> node");
+ let iframe = yield walker.querySelector(walker.rootNode, "#iframe");
+ let {nodes} = yield walker.children(iframe);
+ let frameBody = yield walker.querySelector(nodes[0], "body");
+ players = yield animations.getAnimationPlayersForNode(frameBody);
+
+ // Testing for a hard-coded number of animations here would intermittently
+ // fail depending on how fast or slow the test is (indeed, the test page
+ // contains short transitions, and delayed animations). So just make sure we
+ // at least have the infinitely running animations.
+ ok(players.length >= 4, "All subtree animations were retrieved");
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_animation_keepFinished.js b/devtools/server/tests/browser/browser_animation_keepFinished.js
new file mode 100644
index 000000000..a3240a5e0
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_keepFinished.js
@@ -0,0 +1,54 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the AnimationsActor doesn't report finished animations as removed.
+// Indeed, animations that only have the "finished" playState can be modified
+// still, so we want the AnimationsActor to preserve the corresponding
+// AnimationPlayerActor.
+
+add_task(function* () {
+ let {client, walker, animations} =
+ yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+ info("Retrieve a non-animated node");
+ let node = yield walker.querySelector(walker.rootNode, ".not-animated");
+
+ info("Retrieve the animation player for the node");
+ let players = yield animations.getAnimationPlayersForNode(node);
+ is(players.length, 0, "The node has no animation players");
+
+ info("Listen for new animations");
+ let reportedMutations = [];
+ function onMutations(mutations) {
+ reportedMutations = [...reportedMutations, ...mutations];
+ }
+ animations.on("mutations", onMutations);
+
+ info("Add a short animation on the node");
+ yield node.modifyAttributes([
+ {attributeName: "class", newValue: "short-animation"}
+ ]);
+
+ info("Wait for longer than the animation's duration");
+ yield wait(2000);
+
+ players = yield animations.getAnimationPlayersForNode(node);
+ is(players.length, 0, "The added animation is surely finished");
+
+ is(reportedMutations.length, 1, "Only one mutation was reported");
+ is(reportedMutations[0].type, "added", "The mutation was an addition");
+
+ animations.off("mutations", onMutations);
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
+
+function wait(ms) {
+ return new Promise(resolve => {
+ setTimeout(resolve, ms);
+ });
+}
diff --git a/devtools/server/tests/browser/browser_animation_playPauseIframe.js b/devtools/server/tests/browser/browser_animation_playPauseIframe.js
new file mode 100644
index 000000000..52320b84e
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_playPauseIframe.js
@@ -0,0 +1,51 @@
+/* vim: set 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";
+
+// Check that the AnimationsActor can pause/play all animations even those
+// within iframes.
+
+const URL = MAIN_DOMAIN + "animation.html";
+
+add_task(function* () {
+ info("Creating a test document with 2 iframes containing animated nodes");
+
+ let {client, walker, animations} = yield initAnimationsFrontForUrl(
+ "data:text/html;charset=utf-8," +
+ "<iframe id='i1' src='" + URL + "'></iframe>" +
+ "<iframe id='i2' src='" + URL + "'></iframe>");
+
+ info("Getting the 2 iframe container nodes and animated nodes in them");
+ let nodeInFrame1 = yield getNodeInFrame(walker, "#i1", ".simple-animation");
+ let nodeInFrame2 = yield getNodeInFrame(walker, "#i2", ".simple-animation");
+
+ info("Pause all animations in the test document");
+ yield animations.pauseAll();
+ yield checkState(animations, nodeInFrame1, "paused");
+ yield checkState(animations, nodeInFrame2, "paused");
+
+ info("Play all animations in the test document");
+ yield animations.playAll();
+ yield checkState(animations, nodeInFrame1, "running");
+ yield checkState(animations, nodeInFrame2, "running");
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
+
+function* checkState(animations, nodeFront, playState) {
+ info("Getting the AnimationPlayerFront for the test node");
+ let [player] = yield animations.getAnimationPlayersForNode(nodeFront);
+ yield player.ready;
+ let state = yield player.getCurrentState();
+ is(state.playState, playState,
+ "The playState of the test node is " + playState);
+}
+
+function* getNodeInFrame(walker, frameSelector, nodeSelector) {
+ let iframe = yield walker.querySelector(walker.rootNode, frameSelector);
+ let {nodes} = yield walker.children(iframe);
+ return walker.querySelector(nodes[0], nodeSelector);
+}
diff --git a/devtools/server/tests/browser/browser_animation_playPauseSeveral.js b/devtools/server/tests/browser/browser_animation_playPauseSeveral.js
new file mode 100644
index 000000000..9c52b5f57
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_playPauseSeveral.js
@@ -0,0 +1,92 @@
+/* vim: set 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";
+
+// Check that the AnimationsActor can pause/play all animations at once, and
+// check that it can also pause/play a given list of animations at once.
+
+// List of selectors that match "all" animated nodes in the test page.
+// This list misses a bunch of animated nodes on purpose. Only the ones that
+// have infinite animations are listed. This is done to avoid intermittents
+// caused when finite animations are already done playing by the time the test
+// runs.
+const ALL_ANIMATED_NODES = [".simple-animation", ".multiple-animations",
+ ".delayed-animation"];
+
+add_task(function* () {
+ let {client, walker, animations} =
+ yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+ info("Pause all animations in the test document");
+ yield animations.pauseAll();
+ yield checkStates(walker, animations, ALL_ANIMATED_NODES, "paused");
+
+ info("Play all animations in the test document");
+ yield animations.playAll();
+ yield checkStates(walker, animations, ALL_ANIMATED_NODES, "running");
+
+ info("Pause all animations in the test document using toggleAll");
+ yield animations.toggleAll();
+ yield checkStates(walker, animations, ALL_ANIMATED_NODES, "paused");
+
+ info("Play all animations in the test document using toggleAll");
+ yield animations.toggleAll();
+ yield checkStates(walker, animations, ALL_ANIMATED_NODES, "running");
+
+ info("Play all animations from multiple animated node using toggleSeveral");
+ let players = yield getPlayersFor(walker, animations,
+ [".multiple-animations"]);
+ is(players.length, 2, "Node has 2 animation players");
+ yield animations.toggleSeveral(players, false);
+ let state1 = yield players[0].getCurrentState();
+ is(state1.playState, "running",
+ "The playState of the first player is running");
+ let state2 = yield players[1].getCurrentState();
+ is(state2.playState, "running",
+ "The playState of the second player is running");
+
+ info("Pause one animation from a multiple animated node using toggleSeveral");
+ yield animations.toggleSeveral([players[0]], true);
+ state1 = yield players[0].getCurrentState();
+ is(state1.playState, "paused", "The playState of the first player is paused");
+ state2 = yield players[1].getCurrentState();
+ is(state2.playState, "running",
+ "The playState of the second player is running");
+
+ info("Play the same animation");
+ yield animations.toggleSeveral([players[0]], false);
+ state1 = yield players[0].getCurrentState();
+ is(state1.playState, "running",
+ "The playState of the first player is running");
+ state2 = yield players[1].getCurrentState();
+ is(state2.playState, "running",
+ "The playState of the second player is running");
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
+
+function* checkStates(walker, animations, selectors, playState) {
+ info("Checking the playState of all the nodes that have infinite running " +
+ "animations");
+
+ for (let selector of selectors) {
+ info("Getting the AnimationPlayerFront for node " + selector);
+ let [player] = yield getPlayersFor(walker, animations, selector);
+ yield player.ready();
+ yield checkPlayState(player, selector, playState);
+ }
+}
+
+function* getPlayersFor(walker, animations, selector) {
+ let node = yield walker.querySelector(walker.rootNode, selector);
+ return animations.getAnimationPlayersForNode(node);
+}
+
+function* checkPlayState(player, selector, expectedState) {
+ let state = yield player.getCurrentState();
+ is(state.playState, expectedState,
+ "The playState of node " + selector + " is " + expectedState);
+}
diff --git a/devtools/server/tests/browser/browser_animation_playerState.js b/devtools/server/tests/browser/browser_animation_playerState.js
new file mode 100644
index 000000000..ac5842e39
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_playerState.js
@@ -0,0 +1,123 @@
+/* vim: set 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";
+
+// Check the animation player's initial state
+
+add_task(function* () {
+ let {client, walker, animations} =
+ yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+ yield playerHasAnInitialState(walker, animations);
+ yield playerStateIsCorrect(walker, animations);
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
+
+function* playerHasAnInitialState(walker, animations) {
+ let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+ let [player] = yield animations.getAnimationPlayersForNode(node);
+
+ ok(player.initialState, "The player front has an initial state");
+ ok("startTime" in player.initialState, "Player's state has startTime");
+ ok("currentTime" in player.initialState, "Player's state has currentTime");
+ ok("playState" in player.initialState, "Player's state has playState");
+ ok("playbackRate" in player.initialState, "Player's state has playbackRate");
+ ok("name" in player.initialState, "Player's state has name");
+ ok("duration" in player.initialState, "Player's state has duration");
+ ok("delay" in player.initialState, "Player's state has delay");
+ ok("iterationCount" in player.initialState,
+ "Player's state has iterationCount");
+ ok("fill" in player.initialState, "Player's state has fill");
+ ok("easing" in player.initialState, "Player's state has easing");
+ ok("direction" in player.initialState, "Player's state has direction");
+ ok("isRunningOnCompositor" in player.initialState,
+ "Player's state has isRunningOnCompositor");
+ ok("type" in player.initialState, "Player's state has type");
+ ok("documentCurrentTime" in player.initialState,
+ "Player's state has documentCurrentTime");
+}
+
+function* playerStateIsCorrect(walker, animations) {
+ info("Checking the state of the simple animation");
+
+ let player = yield getAnimationPlayerForNode(walker, animations,
+ ".simple-animation", 0);
+ let state = yield player.getCurrentState();
+ is(state.name, "move", "Name is correct");
+ is(state.duration, 200000, "Duration is correct");
+ // null = infinite count
+ is(state.iterationCount, null, "Iteration count is correct");
+ is(state.fill, "none", "Fill is correct");
+ is(state.easing, "linear", "Easing is correct");
+ is(state.direction, "normal", "Direction is correct");
+ is(state.playState, "running", "PlayState is correct");
+ is(state.playbackRate, 1, "PlaybackRate is correct");
+ is(state.type, "cssanimation", "Type is correct");
+
+ info("Checking the state of the transition");
+
+ player =
+ yield getAnimationPlayerForNode(walker, animations, ".transition", 0);
+ state = yield player.getCurrentState();
+ is(state.name, "width", "Transition name matches transition property");
+ is(state.duration, 500000, "Transition duration is correct");
+ // transitions run only once
+ is(state.iterationCount, 1, "Transition iteration count is correct");
+ is(state.fill, "backwards", "Transition fill is correct");
+ is(state.easing, "linear", "Transition easing is correct");
+ is(state.direction, "normal", "Transition direction is correct");
+ is(state.playState, "running", "Transition playState is correct");
+ is(state.playbackRate, 1, "Transition playbackRate is correct");
+ is(state.type, "csstransition", "Transition type is correct");
+ // chech easing in keyframe
+ let keyframes = yield player.getFrames();
+ is(keyframes.length, 2, "Transition length of keyframe is correct");
+ is(keyframes[0].easing,
+ "ease-out", "Transition kerframes's easing is correct");
+
+ info("Checking the state of one of multiple animations on a node");
+
+ // Checking the 2nd player
+ player = yield getAnimationPlayerForNode(walker, animations,
+ ".multiple-animations", 1);
+ state = yield player.getCurrentState();
+ is(state.name, "glow", "The 2nd animation's name is correct");
+ is(state.duration, 100000, "The 2nd animation's duration is correct");
+ is(state.iterationCount, 5, "The 2nd animation's iteration count is correct");
+ is(state.fill, "both", "The 2nd animation's fill is correct");
+ is(state.easing, "linear", "The 2nd animation's easing is correct");
+ is(state.direction, "reverse", "The 2nd animation's direction is correct");
+ is(state.playState, "running", "The 2nd animation's playState is correct");
+ is(state.playbackRate, 1, "The 2nd animation's playbackRate is correct");
+ // chech easing in keyframe
+ keyframes = yield player.getFrames();
+ is(keyframes.length, 2, "The 2nd animation's length of keyframe is correct");
+ is(keyframes[0].easing,
+ "ease-out", "The 2nd animation's easing of kerframes is correct");
+
+ info("Checking the state of an animation with delay");
+
+ player = yield getAnimationPlayerForNode(walker, animations,
+ ".delayed-animation", 0);
+ state = yield player.getCurrentState();
+ is(state.delay, 5000, "The animation delay is correct");
+
+ info("Checking the state of an transition with delay");
+
+ player = yield getAnimationPlayerForNode(walker, animations,
+ ".delayed-transition", 0);
+ state = yield player.getCurrentState();
+ is(state.delay, 3000, "The transition delay is correct");
+}
+
+function* getAnimationPlayerForNode(walker, animations, nodeSelector, index) {
+ let node = yield walker.querySelector(walker.rootNode, nodeSelector);
+ let players = yield animations.getAnimationPlayersForNode(node);
+ let player = players[index];
+ yield player.ready();
+ return player;
+}
diff --git a/devtools/server/tests/browser/browser_animation_reconstructState.js b/devtools/server/tests/browser/browser_animation_reconstructState.js
new file mode 100644
index 000000000..cd3007b86
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_reconstructState.js
@@ -0,0 +1,38 @@
+/* vim: set 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";
+
+// Check that, even though the AnimationPlayerActor only sends the bits of its
+// state that change, the front reconstructs the whole state everytime.
+
+add_task(function* () {
+ let {client, walker, animations} =
+ yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+ yield playerHasCompleteStateAtAllTimes(walker, animations);
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
+
+function* playerHasCompleteStateAtAllTimes(walker, animations) {
+ let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+ let [player] = yield animations.getAnimationPlayersForNode(node);
+ yield player.ready();
+
+ // Get the list of state key names from the initialstate.
+ let keys = Object.keys(player.initialState);
+
+ // Get the state over and over again and check that the object returned
+ // contains all keys.
+ // Normally, only the currentTime will have changed in between 2 calls.
+ for (let i = 0; i < 10; i++) {
+ yield player.refreshState();
+ keys.forEach(key => {
+ ok(typeof player.state[key] !== "undefined",
+ "The state retrieved has key " + key);
+ });
+ }
+}
diff --git a/devtools/server/tests/browser/browser_animation_refreshTransitions.js b/devtools/server/tests/browser/browser_animation_refreshTransitions.js
new file mode 100644
index 000000000..4cec0df69
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_refreshTransitions.js
@@ -0,0 +1,77 @@
+/* vim: set 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";
+
+// When a transition finishes, no "removed" event is sent because it may still
+// be used, but when it restarts again (transitions back), then a new
+// AnimationPlayerFront should be sent, and the old one should be removed.
+
+add_task(function* () {
+ let {client, walker, animations} =
+ yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+ info("Retrieve the test node");
+ let node = yield walker.querySelector(walker.rootNode, ".all-transitions");
+
+ info("Retrieve the animation players for the node");
+ let players = yield animations.getAnimationPlayersForNode(node);
+ is(players.length, 0, "The node has no animation players yet");
+
+ info("Play a transition by adding the expand class, wait for mutations");
+ let onMutations = expectMutationEvents(animations, 2);
+ let cpow = content.document.querySelector(".all-transitions");
+ cpow.classList.add("expand");
+ let reportedMutations = yield onMutations;
+
+ is(reportedMutations.length, 2, "2 mutation events were received");
+ is(reportedMutations[0].type, "added", "The first event was 'added'");
+ is(reportedMutations[1].type, "added", "The second event was 'added'");
+
+ info("Wait for the transitions to be finished");
+ yield waitForEnd(reportedMutations[0].player);
+ yield waitForEnd(reportedMutations[1].player);
+
+ info("Play the transition back by removing the class, wait for mutations");
+ onMutations = expectMutationEvents(animations, 4);
+ cpow.classList.remove("expand");
+ reportedMutations = yield onMutations;
+
+ is(reportedMutations.length, 4, "4 new mutation events were received");
+ is(reportedMutations.filter(m => m.type === "removed").length, 2,
+ "2 'removed' events were sent (for the old transitions)");
+ is(reportedMutations.filter(m => m.type === "added").length, 2,
+ "2 'added' events were sent (for the new transitions)");
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
+
+function expectMutationEvents(animationsFront, nbOfEvents) {
+ return new Promise(resolve => {
+ let reportedMutations = [];
+ function onMutations(mutations) {
+ reportedMutations = [...reportedMutations, ...mutations];
+ info("Received " + reportedMutations.length + " mutation events, " +
+ "expecting " + nbOfEvents);
+ if (reportedMutations.length === nbOfEvents) {
+ animationsFront.off("mutations", onMutations);
+ resolve(reportedMutations);
+ }
+ }
+
+ info("Start listening for mutation events from the AnimationsFront");
+ animationsFront.on("mutations", onMutations);
+ });
+}
+
+function* waitForEnd(animationFront) {
+ let playState;
+ while (playState !== "finished") {
+ let state = yield animationFront.getCurrentState();
+ playState = state.playState;
+ info("Wait for transition " + animationFront.state.name +
+ " to finish, playState=" + playState);
+ }
+}
diff --git a/devtools/server/tests/browser/browser_animation_setCurrentTime.js b/devtools/server/tests/browser/browser_animation_setCurrentTime.js
new file mode 100644
index 000000000..16dbaa544
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_setCurrentTime.js
@@ -0,0 +1,74 @@
+/* vim: set 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";
+
+// Check that a player's currentTime can be changed and that the AnimationsActor
+// allows changing many players' currentTimes at once.
+
+add_task(function* () {
+ let {client, walker, animations} =
+ yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+ yield testSetCurrentTime(walker, animations);
+ yield testSetCurrentTimes(walker, animations);
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
+
+function* testSetCurrentTime(walker, animations) {
+ info("Retrieve an animated node");
+ let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+
+ info("Retrieve the animation player for the node");
+ let [player] = yield animations.getAnimationPlayersForNode(node);
+
+ ok(player.setCurrentTime, "Player has the setCurrentTime method");
+
+ info("Check that the setCurrentTime method can be called");
+ // Note that we don't check that it sets the animation to the right time here,
+ // this is too prone to intermittent failures, we'll do this later after
+ // pausing the animation. Here we merely test that the method doesn't fail.
+ yield player.setCurrentTime(player.initialState.currentTime + 1000);
+
+ info("Pause the animation so we can really test if setCurrentTime works");
+ yield player.pause();
+ let pausedState = yield player.getCurrentState();
+
+ info("Set the current time to currentTime + 5s");
+ yield player.setCurrentTime(pausedState.currentTime + 5000);
+
+ let updatedState1 = yield player.getCurrentState();
+ is(Math.round(updatedState1.currentTime - pausedState.currentTime), 5000,
+ "The currentTime was updated to +5s");
+
+ info("Set the current time to currentTime - 2s");
+ yield player.setCurrentTime(updatedState1.currentTime - 2000);
+ let updatedState2 = yield player.getCurrentState();
+ is(Math.round(updatedState2.currentTime - updatedState1.currentTime), -2000,
+ "The currentTime was updated to -2s");
+}
+
+function* testSetCurrentTimes(walker, animations) {
+ ok(animations.setCurrentTimes, "The AnimationsActor has the right method");
+
+ info("Retrieve multiple animated node and its animation players");
+
+ let nodeMulti = yield walker.querySelector(walker.rootNode,
+ ".multiple-animations");
+ let players = (yield animations.getAnimationPlayersForNode(nodeMulti));
+
+ ok(players.length > 1, "Node has more than 1 animation player");
+
+ info("Try to set multiple current times at once");
+ yield animations.setCurrentTimes(players, 500, true);
+
+ info("Get the states of players and verify their correctness");
+ for (let i = 0; i < players.length; i++) {
+ let state = yield players[i].getCurrentState();
+ is(state.playState, "paused", `Player ${i + 1} is paused`);
+ is(state.currentTime, 500, `Player ${i + 1} has the right currentTime`);
+ }
+}
diff --git a/devtools/server/tests/browser/browser_animation_setPlaybackRate.js b/devtools/server/tests/browser/browser_animation_setPlaybackRate.js
new file mode 100644
index 000000000..b6d41b51e
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_setPlaybackRate.js
@@ -0,0 +1,51 @@
+/* vim: set 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";
+
+// Check that a player's playbackRate can be changed, and that multiple players
+// can have their rates changed at the same time.
+
+add_task(function* () {
+ let {client, walker, animations} =
+ yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+ info("Retrieve an animated node");
+ let node = yield walker.querySelector(walker.rootNode, ".simple-animation");
+
+ info("Retrieve the animation player for the node");
+ let [player] = yield animations.getAnimationPlayersForNode(node);
+
+ ok(player.setPlaybackRate, "Player has the setPlaybackRate method");
+
+ info("Change the rate to 10");
+ yield player.setPlaybackRate(10);
+
+ info("Query the state again");
+ let state = yield player.getCurrentState();
+ is(state.playbackRate, 10, "The playbackRate was updated");
+
+ info("Change the rate back to 1");
+ yield player.setPlaybackRate(1);
+
+ info("Query the state again");
+ state = yield player.getCurrentState();
+ is(state.playbackRate, 1, "The playbackRate was changed back");
+
+ info("Retrieve several animation players and set their rates");
+ node = yield walker.querySelector(walker.rootNode, "body");
+ let players = yield animations.getAnimationPlayersForNode(node);
+
+ info("Change all animations in <body> to .5 rate");
+ yield animations.setPlaybackRates(players, .5);
+
+ info("Query their states and check they are correct");
+ for (let player of players) {
+ let state = yield player.getCurrentState();
+ is(state.playbackRate, .5, "The playbackRate was updated");
+ }
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_animation_simple.js b/devtools/server/tests/browser/browser_animation_simple.js
new file mode 100644
index 000000000..52daf0084
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_simple.js
@@ -0,0 +1,35 @@
+/* vim: set 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";
+
+// Simple checks for the AnimationsActor
+
+add_task(function* () {
+ let {client, walker, animations} = yield initAnimationsFrontForUrl(
+ "data:text/html;charset=utf-8,<title>test</title><div></div>");
+
+ ok(animations, "The AnimationsFront was created");
+ ok(animations.getAnimationPlayersForNode,
+ "The getAnimationPlayersForNode method exists");
+ ok(animations.toggleAll, "The toggleAll method exists");
+ ok(animations.playAll, "The playAll method exists");
+ ok(animations.pauseAll, "The pauseAll method exists");
+
+ let didThrow = false;
+ try {
+ yield animations.getAnimationPlayersForNode(null);
+ } catch (e) {
+ didThrow = true;
+ }
+ ok(didThrow, "An exception was thrown for a missing NodeActor");
+
+ let invalidNode = yield walker.querySelector(walker.rootNode, "title");
+ let players = yield animations.getAnimationPlayersForNode(invalidNode);
+ ok(Array.isArray(players), "An array of players was returned");
+ is(players.length, 0, "0 players have been returned for the invalid node");
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_animation_updatedState.js b/devtools/server/tests/browser/browser_animation_updatedState.js
new file mode 100644
index 000000000..17d68e9e5
--- /dev/null
+++ b/devtools/server/tests/browser/browser_animation_updatedState.js
@@ -0,0 +1,55 @@
+/* vim: set 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";
+
+// Check the animation player's updated state
+
+add_task(function* () {
+ let {client, walker, animations} =
+ yield initAnimationsFrontForUrl(MAIN_DOMAIN + "animation.html");
+
+ yield playStateIsUpdatedDynamically(walker, animations);
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
+
+function* playStateIsUpdatedDynamically(walker, animations) {
+ info("Getting the test node (which runs a very long animation)");
+ // The animation lasts for 100s, to avoid intermittents.
+ let node = yield walker.querySelector(walker.rootNode, ".long-animation");
+
+ info("Getting the animation player front for this node");
+ let [player] = yield animations.getAnimationPlayersForNode(node);
+ yield player.ready();
+
+ let state = yield player.getCurrentState();
+ is(state.playState, "running",
+ "The playState is running while the animation is running");
+
+ info("Change the animation's currentTime to be near the end and wait for " +
+ "it to finish");
+ let onFinished = waitForAnimationPlayState(player, "finished");
+ // Set the currentTime to 98s, knowing that the animation lasts for 100s.
+ yield player.setCurrentTime(98 * 1000);
+ state = yield onFinished;
+ is(state.playState, "finished",
+ "The animation has ended and the state has been updated");
+ ok(state.currentTime > player.initialState.currentTime,
+ "The currentTime has been updated");
+}
+
+function* waitForAnimationPlayState(player, playState) {
+ let state = {};
+ while (state.playState !== playState) {
+ state = yield player.getCurrentState();
+ yield wait(500);
+ }
+ return state;
+}
+
+function wait(ms) {
+ return new Promise(r => setTimeout(r, ms));
+}
diff --git a/devtools/server/tests/browser/browser_canvasframe_helper_01.js b/devtools/server/tests/browser/browser_canvasframe_helper_01.js
new file mode 100644
index 000000000..7fd943197
--- /dev/null
+++ b/devtools/server/tests/browser/browser_canvasframe_helper_01.js
@@ -0,0 +1,90 @@
+/* vim: set 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";
+
+// Simple CanvasFrameAnonymousContentHelper tests.
+
+// This makes sure the 'domnode' protocol actor type is known when importing
+// highlighter.
+require("devtools/server/actors/inspector");
+const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
+
+const {
+ CanvasFrameAnonymousContentHelper
+} = require("devtools/server/actors/highlighters/utils/markup");
+
+const TEST_URL = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test";
+
+add_task(function* () {
+ let browser = yield addTab(TEST_URL);
+ let doc = browser.contentDocument;
+
+ let nodeBuilder = () => {
+ let root = doc.createElement("div");
+ let child = doc.createElement("div");
+ child.style = "width:200px;height:200px;background:red;";
+ child.id = "child-element";
+ child.className = "child-element";
+ child.textContent = "test element";
+ root.appendChild(child);
+ return root;
+ };
+
+ info("Building the helper");
+ let env = new HighlighterEnvironment();
+ env.initFromWindow(doc.defaultView);
+ let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
+
+ ok(helper.content instanceof AnonymousContent,
+ "The helper owns the AnonymousContent object");
+ ok(helper.getTextContentForElement,
+ "The helper has the getTextContentForElement method");
+ ok(helper.setTextContentForElement,
+ "The helper has the setTextContentForElement method");
+ ok(helper.setAttributeForElement,
+ "The helper has the setAttributeForElement method");
+ ok(helper.getAttributeForElement,
+ "The helper has the getAttributeForElement method");
+ ok(helper.removeAttributeForElement,
+ "The helper has the removeAttributeForElement method");
+ ok(helper.addEventListenerForElement,
+ "The helper has the addEventListenerForElement method");
+ ok(helper.removeEventListenerForElement,
+ "The helper has the removeEventListenerForElement method");
+ ok(helper.getElement,
+ "The helper has the getElement method");
+ ok(helper.scaleRootElement,
+ "The helper has the scaleRootElement method");
+
+ is(helper.getTextContentForElement("child-element"), "test element",
+ "The text content was retrieve correctly");
+ is(helper.getAttributeForElement("child-element", "id"), "child-element",
+ "The ID attribute was retrieve correctly");
+ is(helper.getAttributeForElement("child-element", "class"), "child-element",
+ "The class attribute was retrieve correctly");
+
+ let el = helper.getElement("child-element");
+ ok(el, "The DOMNode-like element was created");
+
+ is(el.getTextContent(), "test element",
+ "The text content was retrieve correctly");
+ is(el.getAttribute("id"), "child-element",
+ "The ID attribute was retrieve correctly");
+ is(el.getAttribute("class"), "child-element",
+ "The class attribute was retrieve correctly");
+
+ info("Destroying the helper");
+ helper.destroy();
+ env.destroy();
+
+ ok(!helper.getTextContentForElement("child-element"),
+ "No text content was retrieved after the helper was destroyed");
+ ok(!helper.getAttributeForElement("child-element", "id"),
+ "No ID attribute was retrieved after the helper was destroyed");
+ ok(!helper.getAttributeForElement("child-element", "class"),
+ "No class attribute was retrieved after the helper was destroyed");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_canvasframe_helper_02.js b/devtools/server/tests/browser/browser_canvasframe_helper_02.js
new file mode 100644
index 000000000..90400c50a
--- /dev/null
+++ b/devtools/server/tests/browser/browser_canvasframe_helper_02.js
@@ -0,0 +1,48 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the CanvasFrameAnonymousContentHelper does not insert content in
+// XUL windows.
+
+// This makes sure the 'domnode' protocol actor type is known when importing
+// highlighter.
+require("devtools/server/actors/inspector");
+
+const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
+
+const {
+ CanvasFrameAnonymousContentHelper
+} = require("devtools/server/actors/highlighters/utils/markup");
+
+add_task(function* () {
+ let browser = yield addTab("about:preferences");
+ let doc = browser.contentDocument;
+
+ let nodeBuilder = () => {
+ let root = doc.createElement("div");
+ let child = doc.createElement("div");
+ child.style = "width:200px;height:200px;background:red;";
+ child.id = "child-element";
+ child.className = "child-element";
+ child.textContent = "test element";
+ root.appendChild(child);
+ return root;
+ };
+
+ info("Building the helper");
+ let env = new HighlighterEnvironment();
+ env.initFromWindow(doc.defaultView);
+ let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
+
+ ok(!helper.content, "The AnonymousContent was not inserted in the window");
+ ok(!helper.getTextContentForElement("child-element"),
+ "No text content is returned");
+
+ env.destroy();
+ helper.destroy();
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_canvasframe_helper_03.js b/devtools/server/tests/browser/browser_canvasframe_helper_03.js
new file mode 100644
index 000000000..85e27c7de
--- /dev/null
+++ b/devtools/server/tests/browser/browser_canvasframe_helper_03.js
@@ -0,0 +1,102 @@
+/* vim: set 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 the CanvasFrameAnonymousContentHelper event handling mechanism.
+
+// This makes sure the 'domnode' protocol actor type is known when importing
+// highlighter.
+require("devtools/server/actors/inspector");
+
+const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
+
+const {
+ CanvasFrameAnonymousContentHelper
+} = require("devtools/server/actors/highlighters/utils/markup");
+
+const TEST_URL = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test";
+
+add_task(function* () {
+ let browser = yield addTab(TEST_URL);
+ let doc = browser.contentDocument;
+
+ let nodeBuilder = () => {
+ let root = doc.createElement("div");
+ let child = doc.createElement("div");
+ child.style = "pointer-events:auto;width:200px;height:200px;background:red;";
+ child.id = "child-element";
+ child.className = "child-element";
+ root.appendChild(child);
+ return root;
+ };
+
+ info("Building the helper");
+ let env = new HighlighterEnvironment();
+ env.initFromWindow(doc.defaultView);
+ let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
+
+ let el = helper.getElement("child-element");
+
+ info("Adding an event listener on the inserted element");
+ let mouseDownHandled = 0;
+ function onMouseDown(e, id) {
+ is(id, "child-element", "The mousedown event was triggered on the element");
+ ok(!e.originalTarget, "The originalTarget property isn't available");
+ mouseDownHandled++;
+ }
+ el.addEventListener("mousedown", onMouseDown);
+
+ info("Synthesizing an event on the inserted element");
+ let onDocMouseDown = once(doc, "mousedown");
+ synthesizeMouseDown(100, 100, doc.defaultView);
+ yield onDocMouseDown;
+
+ is(mouseDownHandled, 1, "The mousedown event was handled once on the element");
+
+ info("Synthesizing an event somewhere else");
+ onDocMouseDown = once(doc, "mousedown");
+ synthesizeMouseDown(400, 400, doc.defaultView);
+ yield onDocMouseDown;
+
+ is(mouseDownHandled, 1, "The mousedown event was not handled on the element");
+
+ info("Removing the event listener");
+ el.removeEventListener("mousedown", onMouseDown);
+
+ info("Synthesizing another event after the listener has been removed");
+ // Using a document event listener to know when the event has been synthesized.
+ onDocMouseDown = once(doc, "mousedown");
+ synthesizeMouseDown(100, 100, doc.defaultView);
+ yield onDocMouseDown;
+
+ is(mouseDownHandled, 1,
+ "The mousedown event hasn't been handled after the listener was removed");
+
+ info("Adding again the event listener");
+ el.addEventListener("mousedown", onMouseDown);
+
+ info("Destroying the helper");
+ env.destroy();
+ helper.destroy();
+
+ info("Synthesizing another event after the helper has been destroyed");
+ // Using a document event listener to know when the event has been synthesized.
+ onDocMouseDown = once(doc, "mousedown");
+ synthesizeMouseDown(100, 100, doc.defaultView);
+ yield onDocMouseDown;
+
+ is(mouseDownHandled, 1,
+ "The mousedown event hasn't been handled after the helper was destroyed");
+
+ gBrowser.removeCurrentTab();
+});
+
+function synthesizeMouseDown(x, y, win) {
+ // We need to make sure the inserted anonymous content can be targeted by the
+ // event right after having been inserted, and so we need to force a sync
+ // reflow.
+ let forceReflow = win.document.documentElement.offsetWidth;
+ EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win);
+}
diff --git a/devtools/server/tests/browser/browser_canvasframe_helper_04.js b/devtools/server/tests/browser/browser_canvasframe_helper_04.js
new file mode 100644
index 000000000..d038f84a0
--- /dev/null
+++ b/devtools/server/tests/browser/browser_canvasframe_helper_04.js
@@ -0,0 +1,98 @@
+/* vim: set 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 the CanvasFrameAnonymousContentHelper re-inserts the content when the
+// page reloads.
+
+// This makes sure the 'domnode' protocol actor type is known when importing
+// highlighter.
+require("devtools/server/actors/inspector");
+const events = require("sdk/event/core");
+
+const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
+
+const {
+ CanvasFrameAnonymousContentHelper
+} = require("devtools/server/actors/highlighters/utils/markup");
+
+const TEST_URL_1 = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test 1";
+const TEST_URL_2 = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test 2";
+
+add_task(function* () {
+ let browser = yield addTab(TEST_URL_2);
+ let doc = browser.contentDocument;
+
+ let nodeBuilder = () => {
+ let root = doc.createElement("div");
+ let child = doc.createElement("div");
+ child.style = "pointer-events:auto;width:200px;height:200px;background:red;";
+ child.id = "child-element";
+ child.className = "child-element";
+ child.textContent = "test content";
+ root.appendChild(child);
+ return root;
+ };
+
+ info("Building the helper");
+ let env = new HighlighterEnvironment();
+ env.initFromWindow(doc.defaultView);
+ let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
+
+ info("Get an element from the helper");
+ let el = helper.getElement("child-element");
+
+ info("Try to access the element");
+ is(el.getAttribute("class"), "child-element",
+ "The attribute is correct before navigation");
+ is(el.getTextContent(), "test content",
+ "The text content is correct before navigation");
+
+ info("Add an event listener on the element");
+ let mouseDownHandled = 0;
+ function onMouseDown(e, id) {
+ is(id, "child-element", "The mousedown event was triggered on the element");
+ mouseDownHandled++;
+ }
+ el.addEventListener("mousedown", onMouseDown);
+
+ info("Synthesizing an event on the element");
+ let onDocMouseDown = once(doc, "mousedown");
+ synthesizeMouseDown(100, 100, doc.defaultView);
+ yield onDocMouseDown;
+ is(mouseDownHandled, 1, "The mousedown event was handled once before navigation");
+
+ info("Navigating to a new page");
+ let loaded = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ content.location = TEST_URL_2;
+ yield loaded;
+ doc = gBrowser.selectedBrowser.contentWindow.document;
+
+ info("Try to access the element again");
+ is(el.getAttribute("class"), "child-element",
+ "The attribute is correct after navigation");
+ is(el.getTextContent(), "test content",
+ "The text content is correct after navigation");
+
+ info("Synthesizing an event on the element again");
+ onDocMouseDown = once(doc, "mousedown");
+ synthesizeMouseDown(100, 100, doc.defaultView);
+ yield onDocMouseDown;
+ is(mouseDownHandled, 1, "The mousedown event was not handled after navigation");
+
+ info("Destroying the helper");
+ env.destroy();
+ helper.destroy();
+
+ gBrowser.removeCurrentTab();
+});
+
+function synthesizeMouseDown(x, y, win) {
+ // We need to make sure the inserted anonymous content can be targeted by the
+ // event right after having been inserted, and so we need to force a sync
+ // reflow.
+ let forceReflow = win.document.documentElement.offsetWidth;
+ EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win);
+}
diff --git a/devtools/server/tests/browser/browser_canvasframe_helper_05.js b/devtools/server/tests/browser/browser_canvasframe_helper_05.js
new file mode 100644
index 000000000..94fb66914
--- /dev/null
+++ b/devtools/server/tests/browser/browser_canvasframe_helper_05.js
@@ -0,0 +1,112 @@
+/* vim: set 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 some edge cases of the CanvasFrameAnonymousContentHelper event handling
+// mechanism.
+
+// This makes sure the 'domnode' protocol actor type is known when importing
+// highlighter.
+require("devtools/server/actors/inspector");
+
+const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
+
+const {
+ CanvasFrameAnonymousContentHelper
+} = require("devtools/server/actors/highlighters/utils/markup");
+
+const TEST_URL = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test";
+
+add_task(function* () {
+ let browser = yield addTab(TEST_URL);
+ let doc = browser.contentDocument;
+
+ let nodeBuilder = () => {
+ let root = doc.createElement("div");
+
+ let parent = doc.createElement("div");
+ parent.style = "pointer-events:auto;width:300px;height:300px;background:yellow;";
+ parent.id = "parent-element";
+ root.appendChild(parent);
+
+ let child = doc.createElement("div");
+ child.style = "pointer-events:auto;width:200px;height:200px;background:red;";
+ child.id = "child-element";
+ parent.appendChild(child);
+
+ return root;
+ };
+
+ info("Building the helper");
+ let env = new HighlighterEnvironment();
+ env.initFromWindow(doc.defaultView);
+ let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
+
+ info("Getting the parent and child elements");
+ let parentEl = helper.getElement("parent-element");
+ let childEl = helper.getElement("child-element");
+
+ info("Adding an event listener on both elements");
+ let mouseDownHandled = [];
+ function onMouseDown(e, id) {
+ mouseDownHandled.push(id);
+ }
+ parentEl.addEventListener("mousedown", onMouseDown);
+ childEl.addEventListener("mousedown", onMouseDown);
+
+ info("Synthesizing an event on the child element");
+ let onDocMouseDown = once(doc, "mousedown");
+ synthesizeMouseDown(100, 100, doc.defaultView);
+ yield onDocMouseDown;
+
+ is(mouseDownHandled.length, 2, "The mousedown event was handled twice");
+ is(mouseDownHandled[0], "child-element",
+ "The mousedown event was handled on the child element");
+ is(mouseDownHandled[1], "parent-element",
+ "The mousedown event was handled on the parent element");
+
+ info("Synthesizing an event on the parent, outside of the child element");
+ mouseDownHandled = [];
+ onDocMouseDown = once(doc, "mousedown");
+ synthesizeMouseDown(250, 250, doc.defaultView);
+ yield onDocMouseDown;
+
+ is(mouseDownHandled.length, 1, "The mousedown event was handled only once");
+ is(mouseDownHandled[0], "parent-element",
+ "The mousedown event was handled on the parent element");
+
+ info("Removing the event listener");
+ parentEl.removeEventListener("mousedown", onMouseDown);
+ childEl.removeEventListener("mousedown", onMouseDown);
+
+ info("Adding an event listener on the parent element only");
+ mouseDownHandled = [];
+ parentEl.addEventListener("mousedown", onMouseDown);
+
+ info("Synthesizing an event on the child element");
+ onDocMouseDown = once(doc, "mousedown");
+ synthesizeMouseDown(100, 100, doc.defaultView);
+ yield onDocMouseDown;
+
+ is(mouseDownHandled.length, 1, "The mousedown event was handled once");
+ is(mouseDownHandled[0], "parent-element",
+ "The mousedown event did bubble to the parent element");
+
+ info("Removing the parent listener");
+ parentEl.removeEventListener("mousedown", onMouseDown);
+
+ env.destroy();
+ helper.destroy();
+
+ gBrowser.removeCurrentTab();
+});
+
+function synthesizeMouseDown(x, y, win) {
+ // We need to make sure the inserted anonymous content can be targeted by the
+ // event right after having been inserted, and so we need to force a sync
+ // reflow.
+ let forceReflow = win.document.documentElement.offsetWidth;
+ EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win);
+}
diff --git a/devtools/server/tests/browser/browser_canvasframe_helper_06.js b/devtools/server/tests/browser/browser_canvasframe_helper_06.js
new file mode 100644
index 000000000..2b137fe26
--- /dev/null
+++ b/devtools/server/tests/browser/browser_canvasframe_helper_06.js
@@ -0,0 +1,100 @@
+/* vim: set 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 support for event propagation stop in the
+// CanvasFrameAnonymousContentHelper event handling mechanism.
+
+// This makes sure the 'domnode' protocol actor type is known when importing
+// highlighter.
+require("devtools/server/actors/inspector");
+
+const {HighlighterEnvironment} = require("devtools/server/actors/highlighters");
+
+const {
+ CanvasFrameAnonymousContentHelper
+} = require("devtools/server/actors/highlighters/utils/markup");
+
+const TEST_URL = "data:text/html;charset=utf-8,CanvasFrameAnonymousContentHelper test";
+
+add_task(function* () {
+ let browser = yield addTab(TEST_URL);
+ let doc = browser.contentDocument;
+
+ let nodeBuilder = () => {
+ let root = doc.createElement("div");
+
+ let parent = doc.createElement("div");
+ parent.style = "pointer-events:auto;width:300px;height:300px;background:yellow;";
+ parent.id = "parent-element";
+ root.appendChild(parent);
+
+ let child = doc.createElement("div");
+ child.style = "pointer-events:auto;width:200px;height:200px;background:red;";
+ child.id = "child-element";
+ parent.appendChild(child);
+
+ return root;
+ };
+
+ info("Building the helper");
+ let env = new HighlighterEnvironment();
+ env.initFromWindow(doc.defaultView);
+ let helper = new CanvasFrameAnonymousContentHelper(env, nodeBuilder);
+
+ info("Getting the parent and child elements");
+ let parentEl = helper.getElement("parent-element");
+ let childEl = helper.getElement("child-element");
+
+ info("Adding an event listener on both elements");
+ let mouseDownHandled = [];
+
+ function onParentMouseDown(e, id) {
+ mouseDownHandled.push(id);
+ }
+ parentEl.addEventListener("mousedown", onParentMouseDown);
+
+ function onChildMouseDown(e, id) {
+ mouseDownHandled.push(id);
+ e.stopPropagation();
+ }
+ childEl.addEventListener("mousedown", onChildMouseDown);
+
+ info("Synthesizing an event on the child element");
+ let onDocMouseDown = once(doc, "mousedown");
+ synthesizeMouseDown(100, 100, doc.defaultView);
+ yield onDocMouseDown;
+
+ is(mouseDownHandled.length, 1, "The mousedown event was handled only once");
+ is(mouseDownHandled[0], "child-element",
+ "The mousedown event was handled on the child element");
+
+ info("Synthesizing an event on the parent, outside of the child element");
+ mouseDownHandled = [];
+ onDocMouseDown = once(doc, "mousedown");
+ synthesizeMouseDown(250, 250, doc.defaultView);
+ yield onDocMouseDown;
+
+ is(mouseDownHandled.length, 1, "The mousedown event was handled only once");
+ is(mouseDownHandled[0], "parent-element",
+ "The mousedown event was handled on the parent element");
+
+ info("Removing the event listener");
+ parentEl.removeEventListener("mousedown", onParentMouseDown);
+ childEl.removeEventListener("mousedown", onChildMouseDown);
+
+ env.destroy();
+ helper.destroy();
+
+ gBrowser.removeCurrentTab();
+});
+
+function synthesizeMouseDown(x, y, win) {
+ // We need to make sure the inserted anonymous content can be targeted by the
+ // event right after having been inserted, and so we need to force a sync
+ // reflow.
+ let forceReflow = win.document.documentElement.offsetWidth;
+ EventUtils.synthesizeMouseAtPoint(x, y, {type: "mousedown"}, win);
+}
diff --git a/devtools/server/tests/browser/browser_directorscript_actors.js b/devtools/server/tests/browser/browser_directorscript_actors.js
new file mode 100644
index 000000000..bdfc8f8f1
--- /dev/null
+++ b/devtools/server/tests/browser/browser_directorscript_actors.js
@@ -0,0 +1,159 @@
+/* vim: set 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";
+
+const {DirectorManagerFront} = require("devtools/shared/fronts/director-manager");
+const {DirectorRegistry} = require("devtools/server/actors/director-registry");
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "director-script-target.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+
+ DirectorRegistry.clear();
+ let directorManager = DirectorManagerFront(client, form);
+
+ yield testDirectorScriptAttachEventAttributes(directorManager);
+ yield testDirectorScriptMessagePort(directorManager);
+ yield testDirectorScriptWindowEval(directorManager);
+ yield testDirectorScriptUnloadOnDetach(directorManager);
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+ DirectorRegistry.clear();
+});
+
+function* testDirectorScriptAttachEventAttributes(directorManager) {
+ let attachEvent = yield installAndEnableDirectorScript(directorManager, {
+ scriptId: "testDirectorScript_attachEventAttributes",
+ scriptCode: "(" + (function () {
+ exports.attach = function () {};
+ }).toString() + ")();",
+ scriptOptions: {
+ attachMethod: "attach"
+ }
+ });
+
+ let { directorScriptId, url } = attachEvent;
+
+ is(directorScriptId, "testDirectorScript_attachEventAttributes",
+ "attach event should contains directorScriptId");
+ is(url, MAIN_DOMAIN + "director-script-target.html");
+}
+
+function* testDirectorScriptMessagePort(directorManager) {
+ let { port } = yield installAndEnableDirectorScript(directorManager, {
+ scriptId: "testDirectorScript_MessagePort",
+ scriptCode: "(" + (function () {
+ exports.attach = function ({port}) {
+ port.onmessage = function (evt) {
+ // echo messages
+ evt.source.postMessage(evt.data);
+ };
+ };
+ }).toString() + ")();",
+ scriptOptions: {
+ attachMethod: "attach"
+ }
+ });
+
+ ok(port && port.postMessage, "testDirector_MessagePort port received");
+
+ // exchange messages over the MessagePort
+ let waitForMessagePortEvent = once(port, "message");
+ // needs to explicit start the port
+ port.start();
+
+ var msg = { k1: "v1", k2: [1, 2, 3] };
+ port.postMessage(msg);
+
+ var reply = yield waitForMessagePortEvent;
+
+ is(JSON.stringify(reply.data), JSON.stringify(msg), "echo reply received on the MessagePortClient");
+}
+
+function* testDirectorScriptWindowEval(directorManager) {
+ let { port } = yield installAndEnableDirectorScript(directorManager, {
+ scriptId: "testDirectorScript_WindowEval",
+ scriptCode: "(" + (function () {
+ exports.attach = function ({window, port}) {
+ var onpageloaded = function () {
+ var globalVarValue = window.eval("globalAccessibleVar;");
+ port.postMessage(globalVarValue);
+ };
+
+ if (window.document && window.document.readyState === "complete") {
+ onpageloaded();
+ } else {
+ window.addEventListener("load", onpageloaded, false);
+ }
+ };
+ }).toString() + ")();",
+ scriptOptions: {
+ attachMethod: "attach"
+ }
+ });
+
+ ok(port, "testDirectorScript_WindowEval port received");
+
+ // exchange messages over the MessagePort
+ let waitForMessagePortEvent = once(port, "message");
+ // needs to explicit start the port
+ port.start();
+
+ var portEvent = yield waitForMessagePortEvent;
+
+ ok(portEvent.data !== "unsecure-eval", "window.eval should be wrapped and safe");
+ is(portEvent.data, "global-value", "globalAccessibleVar should be accessible through window.eval");
+}
+
+function* testDirectorScriptUnloadOnDetach(directorManager) {
+ let { port } = yield installAndEnableDirectorScript(directorManager, {
+ scriptId: "testDirectorScript_unloadOnDetach",
+ scriptCode: "(" + (function () {
+ exports.attach = function ({port, onUnload}) {
+ onUnload(function () {
+ port.postMessage("ONUNLOAD");
+ });
+ };
+ }).toString() + ")();",
+ scriptOptions: {
+ attachMethod: "attach"
+ }
+ });
+
+ ok(port, "testDirectorScript_unloadOnDetach port received");
+ port.start();
+
+ let waitForDetach = once(directorManager, "director-script-detach");
+ let waitForMessage = once(port, "message");
+
+ directorManager.disableByScriptIds(["testDirectorScript_unloadOnDetach"], {reload: false});
+
+ let { directorScriptId } = yield waitForDetach;
+ is(directorScriptId, "testDirectorScript_unloadOnDetach",
+ "detach event should contains directorScriptId");
+
+ let portEvent = yield waitForMessage;
+ is(portEvent.data, "ONUNLOAD", "director-script's exports.onUnload called on detach");
+}
+
+function* installAndEnableDirectorScript(directorManager, directorScriptDef) {
+ let { scriptId } = directorScriptDef;
+
+ DirectorRegistry.install(scriptId, directorScriptDef);
+
+ let waitForAttach = once(directorManager, "director-script-attach");
+ let waitForError = once(directorManager, "director-script-error");
+
+ directorManager.enableByScriptIds([scriptId], {reload: false});
+
+ let attachOrErrorEvent = yield Promise.race([waitForAttach, waitForError]);
+
+ return attachOrErrorEvent;
+}
diff --git a/devtools/server/tests/browser/browser_directorscript_actors_error_events.js b/devtools/server/tests/browser/browser_directorscript_actors_error_events.js
new file mode 100644
index 000000000..0afe16388
--- /dev/null
+++ b/devtools/server/tests/browser/browser_directorscript_actors_error_events.js
@@ -0,0 +1,132 @@
+/* vim: set 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";
+
+const {DirectorManagerFront} = require("devtools/shared/fronts/director-manager");
+const {DirectorRegistry} = require("devtools/server/actors/director-registry");
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "director-script-target.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+
+ DirectorRegistry.clear();
+ let directorManager = DirectorManagerFront(client, form);
+
+ yield testErrorOnRequire(directorManager);
+ yield testErrorOnEvaluate(directorManager);
+ yield testErrorOnAttach(directorManager);
+ yield testErrorOnDetach(directorManager);
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+ DirectorRegistry.clear();
+});
+
+function* testErrorOnRequire(directorManager) {
+ // director script require method should raise a "not implemented" exception
+ let errorOnRequire = yield installAndEnableDirectorScript(directorManager, {
+ scriptId: "testDirectorScript_errorOnRequire",
+ scriptCode: "(" + (function () {
+ // this director script should generate an error event
+ // because require raise a "not implemented" exception
+ require("fake_module");
+ }).toString() + ")();",
+ scriptOptions: {}
+ });
+
+ assertIsDirectorScriptError(errorOnRequire);
+
+ let { message } = errorOnRequire;
+ is(message, "Error: NOT IMPLEMENTED", "error.message contains the expected error message");
+}
+
+function* testErrorOnEvaluate(directorManager) {
+ // director scripts should send an error events if the director script raise an exception on
+ // evaluation
+ let errorOnEvaluate = yield installAndEnableDirectorScript(directorManager, {
+ scriptId: "testDirectorScript_errorOnEvaluate",
+ scriptCode: "(" + (function () {
+ // this will raise an exception evaluating
+ // the director script
+ raise.an_error.during.content_script.load();
+ }).toString() + ")();",
+ scriptOptions: {}
+ });
+ assertIsDirectorScriptError(errorOnEvaluate);
+}
+
+function* testErrorOnAttach(directorManager) {
+ // director scripts should send an error events if the director script raise an exception on
+ // evaluation
+ let errorOnAttach = yield installAndEnableDirectorScript(directorManager, {
+ scriptId: "testDirectorScript_errorOnAttach",
+ scriptCode: "(" + (function () {
+ // this will raise an exception on evaluating
+ // the director script
+ module.exports = function () {
+ raise.an_error.during.content_script.load();
+ };
+ }).toString() + ")();",
+ scriptOptions: {}
+ });
+ assertIsDirectorScriptError(errorOnAttach);
+}
+
+function* testErrorOnDetach(directorManager) {
+ // director scripts should send an error events if the director script raise an exception on
+ // evaluation
+ let attach = yield installAndEnableDirectorScript(directorManager, {
+ scriptId: "testDirectorScript_errorOnDetach",
+ scriptCode: "(" + (function () {
+ module.exports = function ({onUnload}) {
+ // this will raise an exception on unload the director script
+ onUnload(function () {
+ raise_an_error_onunload();
+ });
+ };
+ }).toString() + ")();",
+ scriptOptions: {}
+ });
+
+ let waitForDetach = once(directorManager, "director-script-detach");
+ let waitForError = once(directorManager, "director-script-error");
+
+ directorManager.disableByScriptIds(["testDirectorScript_errorOnDetach"], {reload: false});
+
+ let detach = yield waitForDetach;
+ let error = yield waitForError;
+ ok(detach, "testDirectorScript_errorOnDetach detach event received");
+ ok(error, "testDirectorScript_errorOnDetach detach error received");
+ assertIsDirectorScriptError(error);
+}
+
+function* installAndEnableDirectorScript(directorManager, directorScriptDef) {
+ let { scriptId } = directorScriptDef;
+
+ DirectorRegistry.install(scriptId, directorScriptDef);
+
+ let waitForAttach = once(directorManager, "director-script-attach");
+ let waitForError = once(directorManager, "director-script-error");
+
+ directorManager.enableByScriptIds([scriptId], {reload: false});
+
+ let attachOrErrorEvent = yield Promise.race([waitForAttach, waitForError]);
+
+ return attachOrErrorEvent;
+}
+
+function assertIsDirectorScriptError(error) {
+ ok(!!error.message, "errors should contain a message");
+ ok(!!error.stack, "errors should contain a stack trace");
+ ok(!!error.fileName, "errors should contain a fileName");
+ ok(typeof error.columnNumber == "number", "errors should contain a columnNumber");
+ ok(typeof error.lineNumber == "number", "errors should contain a lineNumber");
+
+ ok(!!error.directorScriptId, "errors should contain a directorScriptId");
+}
diff --git a/devtools/server/tests/browser/browser_directorscript_actors_exports.js b/devtools/server/tests/browser/browser_directorscript_actors_exports.js
new file mode 100644
index 000000000..f9ef56f51
--- /dev/null
+++ b/devtools/server/tests/browser/browser_directorscript_actors_exports.js
@@ -0,0 +1,87 @@
+/* vim: set 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";
+
+const {DirectorManagerFront} = require("devtools/shared/fronts/director-manager");
+const {DirectorRegistry} = require("devtools/server/actors/director-registry");
+
+DirectorRegistry.clear();
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "director-script-target.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+
+ DirectorRegistry.clear();
+ let directorManager = DirectorManagerFront(client, form);
+
+ // director scripts attach method defaults to module.exports
+ let attachModuleExports = yield testDirectorScriptExports(directorManager, {
+ scriptId: "testDirectorScript_moduleExports",
+ scriptCode: "(" + (function () {
+ module.exports = function () {};
+ }).toString() + ")();",
+ scriptOptions: {}
+ });
+ ok(attachModuleExports.port, "testDirectorScript_moduleExports attach event received");
+
+ // director scripts attach method can be configured using the attachMethod scriptOptions
+ let attachExportsAttach = yield testDirectorScriptExports(directorManager, {
+ scriptId: "testDirectorScript_exportsAttach",
+ scriptCode: "(" + (function () {
+ exports.attach = function () {};
+ }).toString() + ")();",
+ scriptOptions: {
+ attachMethod: "attach"
+ }
+ });
+ ok(attachExportsAttach.port, "testDirectorScript_exportsAttach attach event received");
+
+ // director scripts without an attach method generates an error event
+ let errorUndefinedAttachMethod = yield testDirectorScriptExports(directorManager, {
+ scriptId: "testDirectorScript_undefinedAttachMethod",
+ scriptCode: "(" + (function () {
+ // this director script should raise an error
+ // because it doesn't export any attach method
+ }).toString() + ")();",
+ scriptOptions: {
+ attachMethod: "attach"
+ }
+ });
+ let { message } = errorUndefinedAttachMethod;
+ ok(!!message, "testDirectorScript_undefinedAttachMethod error event received");
+ assertIsDirectorScriptError(errorUndefinedAttachMethod);
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+ DirectorRegistry.clear();
+});
+
+function assertIsDirectorScriptError(error) {
+ ok(!!error.message, "errors should contain a message");
+ ok(!!error.stack, "errors should contain a stack trace");
+ ok(!!error.fileName, "errors should contain a fileName");
+ ok(typeof error.columnNumber == "number", "errors should contain a columnNumber");
+ ok(typeof error.lineNumber == "number", "errors should contain a lineNumber");
+
+ ok(!!error.directorScriptId, "errors should contain a directorScriptId");
+}
+
+function* testDirectorScriptExports(directorManager, directorScriptDef) {
+ let { scriptId } = directorScriptDef;
+
+ DirectorRegistry.install(scriptId, directorScriptDef);
+
+ let waitForAttach = once(directorManager, "director-script-attach");
+ let waitForError = once(directorManager, "director-script-error");
+ directorManager.enableByScriptIds([scriptId], {reload: false});
+
+ let attachOrErrorEvent = yield Promise.race([waitForAttach, waitForError]);
+
+ return attachOrErrorEvent;
+}
diff --git a/devtools/server/tests/browser/browser_markers-cycle-collection.js b/devtools/server/tests/browser/browser_markers-cycle-collection.js
new file mode 100644
index 000000000..dc33375f2
--- /dev/null
+++ b/devtools/server/tests/browser/browser_markers-cycle-collection.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get "nsCycleCollector::Collect" and
+ * "nsCycleCollector::ForgetSkippable" markers when we force cycle collection.
+ */
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+
+add_task(function* () {
+ // This test runs very slowly on linux32 debug EC2 instances.
+ requestLongerTimeout(2);
+
+ let browser = yield addTab(MAIN_DOMAIN + "doc_force_cc.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = PerformanceFront(client, form);
+ yield front.connect();
+ let rec = yield front.startRecording({ withMarkers: true });
+
+ let markers = yield waitForMarkerType(front, ["nsCycleCollector::Collect", "nsCycleCollector::ForgetSkippable"]);
+ yield front.stopRecording(rec);
+
+ ok(markers.some(m => m.name === "nsCycleCollector::Collect"), "got some nsCycleCollector::Collect markers");
+ ok(markers.some(m => m.name === "nsCycleCollector::ForgetSkippable"), "got some nsCycleCollector::Collect markers");
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_markers-docloading-01.js b/devtools/server/tests/browser/browser_markers-docloading-01.js
new file mode 100644
index 000000000..3c82d56c4
--- /dev/null
+++ b/devtools/server/tests/browser/browser_markers-docloading-01.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get DOMContentLoaded and Load markers
+ */
+
+const { TimelineFront } = require("devtools/shared/fronts/timeline");
+const MARKER_NAMES = ["document::DOMContentLoaded", "document::Load"];
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "doc_innerHTML.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = TimelineFront(client, form);
+ let rec = yield front.start({ withMarkers: true });
+
+ front.once("doc-loading", e => {
+ ok(false, "Should not be emitting doc-loading events.");
+ });
+
+ executeSoon(() => doc.location.reload());
+
+ yield waitForMarkerType(front, MARKER_NAMES, () => true, e => e, "markers");
+ yield front.stop(rec);
+
+ ok(true, "Found the required marker names.");
+
+ // Wait some more time to make sure the 'doc-loading' events never get fired.
+ yield DevToolsUtils.waitForTime(1000);
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_markers-docloading-02.js b/devtools/server/tests/browser/browser_markers-docloading-02.js
new file mode 100644
index 000000000..0142ea0cd
--- /dev/null
+++ b/devtools/server/tests/browser/browser_markers-docloading-02.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get DOMContentLoaded and Load markers
+ */
+
+const { TimelineFront } = require("devtools/shared/fronts/timeline");
+const MARKER_NAMES = ["document::DOMContentLoaded", "document::Load"];
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "doc_innerHTML.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = TimelineFront(client, form);
+ let rec = yield front.start({ withMarkers: true, withDocLoadingEvents: true });
+
+ yield new Promise(resolve => {
+ front.once("doc-loading", resolve);
+ doc.location.reload();
+ });
+
+ ok(true, "At least one doc-loading event got fired.");
+
+ yield waitForMarkerType(front, MARKER_NAMES, () => true, e => e, "markers");
+ yield front.stop(rec);
+
+ ok(true, "Found the required marker names.");
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_markers-docloading-03.js b/devtools/server/tests/browser/browser_markers-docloading-03.js
new file mode 100644
index 000000000..1960da4da
--- /dev/null
+++ b/devtools/server/tests/browser/browser_markers-docloading-03.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get DOMContentLoaded and Load markers
+ */
+
+const { TimelineFront } = require("devtools/shared/fronts/timeline");
+const MARKER_NAMES = ["document::DOMContentLoaded", "document::Load"];
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "doc_innerHTML.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = TimelineFront(client, form);
+ let rec = yield front.start({ withDocLoadingEvents: true });
+
+ waitForMarkerType(front, MARKER_NAMES, () => true, e => e, "markers").then(e => {
+ ok(false, "Should not be emitting doc-loading markers.");
+ });
+
+ yield new Promise(resolve => {
+ front.once("doc-loading", resolve);
+ doc.location.reload();
+ });
+
+ ok(true, "At least one doc-loading event got fired.");
+
+ yield front.stop(rec);
+
+ // Wait some more time to make sure the 'doc-loading' markers never get fired.
+ yield DevToolsUtils.waitForTime(1000);
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_markers-gc.js b/devtools/server/tests/browser/browser_markers-gc.js
new file mode 100644
index 000000000..559b19161
--- /dev/null
+++ b/devtools/server/tests/browser/browser_markers-gc.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get "GarbageCollection" markers.
+ */
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+const MARKER_NAME = "GarbageCollection";
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "doc_force_gc.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = PerformanceFront(client, form);
+ yield front.connect();
+ let rec = yield front.startRecording({ withMarkers: true });
+
+ let markers = yield waitForMarkerType(front, MARKER_NAME);
+ yield front.stopRecording(rec);
+
+ ok(markers.some(m => m.name === MARKER_NAME), `got some ${MARKER_NAME} markers`);
+ ok(markers.every(({causeName}) => typeof causeName === "string"),
+ "All markers have a causeName.");
+ ok(markers.every(({cycle}) => typeof cycle === "number"),
+ "All markers have a `cycle` ID.");
+
+ markers = rec.getMarkers();
+
+ // Bug 1197646
+ let ordered = true;
+ markers.reduce((previousStart, current, i) => {
+ if (i === 0) {
+ return current.start;
+ }
+ if (current.start < previousStart) {
+ ok(false, `markers must be in order. ${current.name} marker has later start time (${current.start}) thanprevious: ${previousStart}`);
+ ordered = false;
+ }
+ return current.start;
+ });
+
+ is(ordered, true, "All GC and non-GC markers are in order by start time.");
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_markers-minor-gc.js b/devtools/server/tests/browser/browser_markers-minor-gc.js
new file mode 100644
index 000000000..332764348
--- /dev/null
+++ b/devtools/server/tests/browser/browser_markers-minor-gc.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get "MinorGC" markers when we continue to steadily allocate
+ * objects.
+ */
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+
+add_task(function* () {
+ // This test runs very slowly on linux32 debug EC2 instances.
+ requestLongerTimeout(2);
+
+ let doc = yield addTab(MAIN_DOMAIN + "doc_allocations.html");
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = PerformanceFront(client, form);
+ yield front.connect();
+ let rec = yield front.startRecording({ withMarkers: true });
+
+ let markers = yield waitForMarkerType(front, ["MinorGC"]);
+ yield front.stopRecording(rec);
+
+ ok(markers.some(m => m.name === "MinorGC" && m.causeName),
+ "got some MinorGC markers");
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_markers-parse-html.js b/devtools/server/tests/browser/browser_markers-parse-html.js
new file mode 100644
index 000000000..bd4f479c0
--- /dev/null
+++ b/devtools/server/tests/browser/browser_markers-parse-html.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get "Parse HTML" markers.
+ */
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+const MARKER_NAME = "Parse HTML";
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "doc_innerHTML.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = PerformanceFront(client, form);
+ yield front.connect();
+ let rec = yield front.startRecording({ withMarkers: true });
+
+ let markers = yield waitForMarkerType(front, MARKER_NAME);
+ yield front.stopRecording(rec);
+
+ ok(markers.some(m => m.name === MARKER_NAME), `got some ${MARKER_NAME} markers`);
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_markers-styles.js b/devtools/server/tests/browser/browser_markers-styles.js
new file mode 100644
index 000000000..a3dffe8b5
--- /dev/null
+++ b/devtools/server/tests/browser/browser_markers-styles.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get "Styles" markers with correct meta.
+ */
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+const MARKER_NAME = "Styles";
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = PerformanceFront(client, form);
+ yield front.connect();
+ let rec = yield front.startRecording({ withMarkers: true });
+
+ let markers = yield waitForMarkerType(front, MARKER_NAME, function (markers) {
+ return markers.some(({restyleHint}) => restyleHint != void 0);
+ });
+
+ yield front.stopRecording(rec);
+
+ ok(markers.some(m => m.name === MARKER_NAME), `got some ${MARKER_NAME} markers`);
+ ok(markers.some(({restyleHint}) => restyleHint != void 0),
+ "Some markers have a restyleHint.");
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_markers-timestamp.js b/devtools/server/tests/browser/browser_markers-timestamp.js
new file mode 100644
index 000000000..428499502
--- /dev/null
+++ b/devtools/server/tests/browser/browser_markers-timestamp.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we get a "TimeStamp" marker.
+ */
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+const { pmmConsoleMethod, pmmLoadFrameScripts, pmmClearFrameScripts } = require("devtools/client/performance/test/helpers/profiler-mm-utils");
+const MARKER_NAME = "TimeStamp";
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = PerformanceFront(client, form);
+ yield front.connect();
+ let rec = yield front.startRecording({ withMarkers: true });
+
+ pmmLoadFrameScripts(gBrowser);
+ pmmConsoleMethod("timeStamp");
+ pmmConsoleMethod("timeStamp", "myLabel");
+
+ let markers = yield waitForMarkerType(front, MARKER_NAME, markers => markers.length >= 2);
+
+ yield front.stopRecording(rec);
+
+ ok(markers.every(({stack}) => typeof stack === "number"), "All markers have stack references.");
+ ok(markers.every(({name}) => name === "TimeStamp"), "All markers found are TimeStamp markers");
+ ok(markers.length === 2, "found 2 TimeStamp markers");
+ ok(markers.every(({start, end}) => typeof start === "number" && start === end),
+ "All markers have equal start and end times");
+ is(markers[0].causeName, void 0, "Unlabeled timestamps have an empty causeName");
+ is(markers[1].causeName, "myLabel", "Labeled timestamps have correct causeName");
+
+ pmmClearFrameScripts();
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_navigateEvents.js b/devtools/server/tests/browser/browser_navigateEvents.js
new file mode 100644
index 000000000..f8652f197
--- /dev/null
+++ b/devtools/server/tests/browser/browser_navigateEvents.js
@@ -0,0 +1,160 @@
+/* vim: set 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";
+
+const URL1 = MAIN_DOMAIN + "navigate-first.html";
+const URL2 = MAIN_DOMAIN + "navigate-second.html";
+
+var events = require("sdk/event/core");
+var client;
+
+SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]});
+
+// State machine to check events order
+var i = 0;
+function assertEvent(event, data) {
+ let x = 0;
+ switch (i++) {
+ case x++:
+ is(event, "request", "Get first page load");
+ is(data, URL1);
+ break;
+ case x++:
+ is(event, "load-new-document", "Ask to load the second page");
+ break;
+ case x++:
+ is(event, "unload-dialog", "We get the dialog on first page unload");
+ break;
+ case x++:
+ is(event, "will-navigate", "The very first event is will-navigate on server side");
+ is(data.newURI, URL2, "newURI property is correct");
+ break;
+ case x++:
+ is(event, "request", "RDP is async with messageManager, the request happens after will-navigate");
+ is(data, URL2);
+ break;
+ case x++:
+ is(event, "tabNavigated", "After the request, the client receive tabNavigated");
+ is(data.state, "start", "state is start");
+ is(data.url, URL2, "url property is correct");
+ is(data.nativeConsoleAPI, true, "nativeConsoleAPI is correct");
+ break;
+ case x++:
+ is(event, "DOMContentLoaded");
+ is(content.document.readyState, "interactive");
+ break;
+ case x++:
+ is(event, "load");
+ is(content.document.readyState, "complete");
+ break;
+ case x++:
+ is(event, "navigate", "Then once the second doc is loaded, we get the navigate event");
+ is(content.document.readyState, "complete", "navigate is emitted only once the document is fully loaded");
+ break;
+ case x++:
+ is(event, "tabNavigated", "Finally, the receive the client event");
+ is(data.state, "stop", "state is stop");
+ is(data.url, URL2, "url property is correct");
+ is(data.nativeConsoleAPI, true, "nativeConsoleAPI is correct");
+
+ // End of test!
+ cleanup();
+ break;
+ }
+}
+
+function waitForOnBeforeUnloadDialog(browser, callback) {
+ browser.addEventListener("DOMWillOpenModalDialog", function onModalDialog() {
+ browser.removeEventListener("DOMWillOpenModalDialog", onModalDialog, true);
+
+ executeSoon(() => {
+ let stack = browser.parentNode;
+ let dialogs = stack.getElementsByTagName("tabmodalprompt");
+ let {button0, button1} = dialogs[0].ui;
+ callback(button0, button1);
+ });
+ }, true);
+}
+
+var httpObserver = function (subject, topic, state) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ let url = channel.URI.spec;
+ // Only listen for our document request, as many other requests can happen
+ if (url == URL1 || url == URL2) {
+ assertEvent("request", url);
+ }
+};
+Services.obs.addObserver(httpObserver, "http-on-modify-request", false);
+
+function onDOMContentLoaded() {
+ assertEvent("DOMContentLoaded");
+}
+function onLoad() {
+ assertEvent("load");
+}
+
+function getServerTabActor(callback) {
+ // Ensure having a minimal server
+ initDebuggerServer();
+
+ // Connect to this tab
+ let transport = DebuggerServer.connectPipe();
+ client = new DebuggerClient(transport);
+ connectDebuggerClient(client).then(form => {
+ let actorID = form.actor;
+ client.attachTab(actorID, function (aResponse, aTabClient) {
+ // !Hack! Retrieve a server side object, the BrowserTabActor instance
+ let tabActor = DebuggerServer._searchAllConnectionsForActor(actorID);
+ callback(tabActor);
+ });
+ });
+
+ client.addListener("tabNavigated", function (aEvent, aPacket) {
+ assertEvent("tabNavigated", aPacket);
+ });
+}
+
+function test() {
+ // Open a test tab
+ addTab(URL1).then(function (browser) {
+ let doc = browser.contentDocument;
+ getServerTabActor(function (tabActor) {
+ // In order to listen to internal will-navigate/navigate events
+ events.on(tabActor, "will-navigate", function (data) {
+ assertEvent("will-navigate", data);
+ });
+ events.on(tabActor, "navigate", function (data) {
+ assertEvent("navigate", data);
+ });
+
+ // Start listening for page load events
+ browser.addEventListener("DOMContentLoaded", onDOMContentLoaded, true);
+ browser.addEventListener("load", onLoad, true);
+
+ // Listen for alert() call being made in navigate-first during unload
+ waitForOnBeforeUnloadDialog(browser, function (btnLeave, btnStay) {
+ assertEvent("unload-dialog");
+ // accept to quit this page to another
+ btnLeave.click();
+ });
+
+ // Load another document in this doc to dispatch these events
+ assertEvent("load-new-document");
+ content.location = URL2;
+ });
+
+ });
+}
+
+function cleanup() {
+ let browser = gBrowser.selectedBrowser;
+ browser.removeEventListener("DOMContentLoaded", onDOMContentLoaded);
+ browser.removeEventListener("load", onLoad);
+ client.close().then(function () {
+ Services.obs.addObserver(httpObserver, "http-on-modify-request", false);
+ DebuggerServer.destroy();
+ finish();
+ });
+}
diff --git a/devtools/server/tests/browser/browser_perf-allocation-data.js b/devtools/server/tests/browser/browser_perf-allocation-data.js
new file mode 100644
index 000000000..3d4a94dee
--- /dev/null
+++ b/devtools/server/tests/browser/browser_perf-allocation-data.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we have allocation data coming from the front.
+ */
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "doc_allocations.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = PerformanceFront(client, form);
+ yield front.connect();
+
+ let rec = yield front.startRecording({ withMarkers: true, withAllocations: true, withTicks: true });
+
+ yield waitUntil(() => rec.getAllocations().frames.length);
+ yield waitUntil(() => rec.getAllocations().timestamps.length);
+ yield waitUntil(() => rec.getAllocations().sizes.length);
+ yield waitUntil(() => rec.getAllocations().sites.length);
+
+ yield front.stopRecording(rec);
+
+ let { frames, timestamps, sizes, sites } = rec.getAllocations();
+
+ is(timestamps.length, sizes.length, "we have the same amount of timestamps and sizes");
+ ok(timestamps.every(time => time > 0 && typeof time === "number"), "all timestamps have numeric values");
+ ok(sizes.every(n => n > 0 && typeof n === "number"), "all sizes are positive numbers");
+
+ yield front.destroy();
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_perf-profiler-01.js b/devtools/server/tests/browser/browser_perf-profiler-01.js
new file mode 100644
index 000000000..36d200f01
--- /dev/null
+++ b/devtools/server/tests/browser/browser_perf-profiler-01.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the profiler connection front does not activate the built-in
+ * profiler module if not necessary, and doesn't deactivate it when
+ * a recording is stopped.
+ */
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+const { pmmIsProfilerActive, pmmStopProfiler, pmmLoadFrameScripts } = require("devtools/client/performance/test/helpers/profiler-mm-utils");
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = PerformanceFront(client, form);
+ yield front.connect();
+
+ pmmLoadFrameScripts(gBrowser);
+
+ ok(!(yield pmmIsProfilerActive()),
+ "The built-in profiler module should not have been automatically started.");
+
+ let rec = yield front.startRecording();
+ yield front.stopRecording(rec);
+ ok((yield pmmIsProfilerActive()),
+ "The built-in profiler module should still be active (1).");
+
+ rec = yield front.startRecording();
+ yield front.stopRecording(rec);
+ ok((yield pmmIsProfilerActive()),
+ "The built-in profiler module should still be active (2).");
+
+ yield front.destroy();
+ yield client.close();
+
+ ok(!(yield pmmIsProfilerActive()),
+ "The built-in profiler module should no longer be active.");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_perf-profiler-02.js b/devtools/server/tests/browser/browser_perf-profiler-02.js
new file mode 100644
index 000000000..29842cef7
--- /dev/null
+++ b/devtools/server/tests/browser/browser_perf-profiler-02.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the built-in profiler module doesn't deactivate when the toolbox
+ * is destroyed if there are other consumers using it.
+ */
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+const { pmmIsProfilerActive, pmmStopProfiler, pmmLoadFrameScripts } = require("devtools/client/performance/test/helpers/profiler-mm-utils");
+
+add_task(function* () {
+ yield addTab(MAIN_DOMAIN + "doc_perf.html");
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let firstFront = PerformanceFront(client, form);
+ yield firstFront.connect();
+
+ pmmLoadFrameScripts(gBrowser);
+
+ yield firstFront.startRecording();
+
+ yield addTab(MAIN_DOMAIN + "doc_perf.html");
+ let client2 = new DebuggerClient(DebuggerServer.connectPipe());
+ let form2 = yield connectDebuggerClient(client2);
+ let secondFront = PerformanceFront(client2, form2);
+ yield secondFront.connect();
+ pmmLoadFrameScripts(gBrowser);
+
+ yield secondFront.startRecording();
+
+ // Manually teardown the tabs so we can check profiler status
+ yield secondFront.destroy();
+ yield client2.close();
+ ok((yield pmmIsProfilerActive()),
+ "The built-in profiler module should still be active.");
+
+ yield firstFront.destroy();
+ yield client.close();
+ ok(!(yield pmmIsProfilerActive()),
+ "The built-in profiler module should no longer be active.");
+
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_perf-profiler-03.js b/devtools/server/tests/browser/browser_perf-profiler-03.js
new file mode 100644
index 000000000..28d87fe45
--- /dev/null
+++ b/devtools/server/tests/browser/browser_perf-profiler-03.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the built-in profiler module is not reactivated if no other
+ * consumer was using it over the remote debugger protocol, and ensures
+ * that the actor will work properly even in such cases (e.g. the Gecko Profiler
+ * addon was installed and automatically activated the profiler module).
+ */
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+const { pmmIsProfilerActive, pmmStartProfiler, pmmStopProfiler, pmmLoadFrameScripts, pmmClearFrameScripts } = require("devtools/client/performance/test/helpers/profiler-mm-utils");
+
+add_task(function* () {
+ // Ensure the profiler is already running when the test starts.
+ pmmLoadFrameScripts(gBrowser);
+ let entries = 1000000;
+ let interval = 1;
+ let features = ["js"];
+ yield pmmStartProfiler({ entries, interval, features });
+
+ ok((yield pmmIsProfilerActive()),
+ "The built-in profiler module should still be active.");
+
+ yield addTab(MAIN_DOMAIN + "doc_perf.html");
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let firstFront = PerformanceFront(client, form);
+ yield firstFront.connect();
+
+ let recording = yield firstFront.startRecording();
+
+ yield addTab(MAIN_DOMAIN + "doc_perf.html");
+ let client2 = new DebuggerClient(DebuggerServer.connectPipe());
+ let form2 = yield connectDebuggerClient(client2);
+ let secondFront = PerformanceFront(client2, form2);
+ yield secondFront.connect();
+
+ yield secondFront.destroy();
+ yield client2.close();
+ ok((yield pmmIsProfilerActive()),
+ "The built-in profiler module should still be active.");
+
+ yield firstFront.destroy();
+ yield client.close();
+ ok(!(yield pmmIsProfilerActive()),
+ "The built-in profiler module should have been automatically stopped.");
+
+ pmmClearFrameScripts();
+
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_perf-realtime-markers.js b/devtools/server/tests/browser/browser_perf-realtime-markers.js
new file mode 100644
index 000000000..b9eab211c
--- /dev/null
+++ b/devtools/server/tests/browser/browser_perf-realtime-markers.js
@@ -0,0 +1,93 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test functionality of real time markers.
+ */
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = PerformanceFront(client, form);
+ yield front.connect();
+
+ let lastMemoryDelta = 0;
+ let lastTickDelta = 0;
+
+ let counters = {
+ markers: [],
+ memory: [],
+ ticks: []
+ };
+
+ let deferreds = {
+ markers: defer(),
+ memory: defer(),
+ ticks: defer()
+ };
+
+ front.on("timeline-data", handler);
+
+ let rec = yield front.startRecording({ withMarkers: true, withMemory: true, withTicks: true });
+ yield Promise.all(Object.keys(deferreds).map(type => deferreds[type].promise));
+ yield front.stopRecording(rec);
+ front.off("timeline-data", handler);
+
+ is(counters.markers.length, 1, "one marker event fired.");
+ is(counters.memory.length, 3, "three memory events fired.");
+ is(counters.ticks.length, 3, "three ticks events fired.");
+
+ yield front.destroy();
+ yield client.close();
+ gBrowser.removeCurrentTab();
+
+ function handler(name, data) {
+ if (name === "markers") {
+ if (counters.markers.length >= 1) { return; }
+ ok(data.markers[0].start, "received atleast one marker with `start`");
+ ok(data.markers[0].end, "received atleast one marker with `end`");
+ ok(data.markers[0].name, "received atleast one marker with `name`");
+
+ counters.markers.push(data.markers);
+ }
+ else if (name === "memory") {
+ if (counters.memory.length >= 3) { return; }
+ let { delta, measurement } = data;
+ is(typeof delta, "number", "received `delta` in memory event");
+ ok(delta > lastMemoryDelta, "received `delta` in memory event");
+ ok(measurement.total, "received `total` in memory event");
+
+ counters.memory.push({ delta, measurement });
+ lastMemoryDelta = delta;
+ }
+ else if (name === "ticks") {
+ if (counters.ticks.length >= 3) { return; }
+ let { delta, timestamps } = data;
+ ok(delta > lastTickDelta, "received `delta` in ticks event");
+
+ // Timestamps aren't guaranteed to always contain tick events, since
+ // they're dependent on the refresh driver, which may be blocked.
+
+ counters.ticks.push({ delta, timestamps });
+ lastTickDelta = delta;
+ }
+ else if (name === "frames") {
+ // Nothing to do here.
+ }
+ else {
+ ok(false, `Received unknown event: ${name}`);
+ }
+
+ if (name === "markers" && counters[name].length === 1 ||
+ name === "memory" && counters[name].length === 3 ||
+ name === "ticks" && counters[name].length === 3) {
+ deferreds[name].resolve();
+ }
+ }
+});
diff --git a/devtools/server/tests/browser/browser_perf-recording-actor-01.js b/devtools/server/tests/browser/browser_perf-recording-actor-01.js
new file mode 100644
index 000000000..683493121
--- /dev/null
+++ b/devtools/server/tests/browser/browser_perf-recording-actor-01.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests the state of a recording rec from start to finish for recording,
+ * completed, and rec data.
+ */
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = PerformanceFront(client, form);
+ yield front.connect();
+
+ let rec = yield front.startRecording({ withMarkers: true, withTicks: true, withMemory: true });
+ ok(rec.isRecording(), "RecordingModel is recording when created");
+ yield busyWait(100);
+ yield waitUntil(() => rec.getMemory().length);
+ ok(true, "RecordingModel populates memory while recording");
+ yield waitUntil(() => rec.getTicks().length);
+ ok(true, "RecordingModel populates ticks while recording");
+ yield waitUntil(() => rec.getMarkers().length);
+ ok(true, "RecordingModel populates markers while recording");
+
+ ok(!rec.isCompleted(), "RecordingModel is not completed when still recording");
+
+ let stopping = once(front, "recording-stopping");
+ let stopped = once(front, "recording-stopped");
+ front.stopRecording(rec);
+
+ yield stopping;
+ ok(!rec.isRecording(), "on 'recording-stopping', model is no longer recording");
+ // This handler should be called BEFORE "recording-stopped" is called, as
+ // there is some delay, but in the event where "recording-stopped" finishes
+ // before we check here, ensure that we're atleast in the right state
+ if (rec.getProfile()) {
+ ok(rec.isCompleted(), "recording is completed once it has profile data");
+ } else {
+ ok(!rec.isCompleted(), "recording is not yet completed on 'recording-stopping'");
+ ok(rec.isFinalizing(), "recording is considered finalizing between 'recording-stopping' and 'recording-stopped'");
+ }
+
+ yield stopped;
+ ok(!rec.isRecording(), "on 'recording-stopped', model is still no longer recording");
+ ok(rec.isCompleted(), "on 'recording-stopped', model is considered 'complete'");
+
+ checkSystemInfo(rec, "Host");
+ checkSystemInfo(rec, "Client");
+
+ // Export and import a rec, and ensure it has the correct state.
+ let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+ yield rec.exportRecording(file);
+
+ let importedModel = yield front.importRecording(file);
+
+ ok(importedModel.isCompleted(), "All imported recordings should be completed");
+ ok(!importedModel.isRecording(), "All imported recordings should not be recording");
+ ok(importedModel.isImported(), "All imported recordings should be considerd imported");
+
+ checkSystemInfo(importedModel, "Host");
+ checkSystemInfo(importedModel, "Client");
+
+ yield front.destroy();
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
+
+function checkSystemInfo(recording, type) {
+ let data = recording[`get${type}SystemInfo`]();
+ for (let field of ["appid", "apptype", "vendor", "name", "version"]) {
+ ok(data[field], `get${type}SystemInfo() has ${field} property`);
+ }
+}
diff --git a/devtools/server/tests/browser/browser_perf-recording-actor-02.js b/devtools/server/tests/browser/browser_perf-recording-actor-02.js
new file mode 100644
index 000000000..8337ad2ef
--- /dev/null
+++ b/devtools/server/tests/browser/browser_perf-recording-actor-02.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that buffer status is correctly updated in recording models.
+ */
+
+var BUFFER_SIZE = 20000;
+var config = { bufferSize: BUFFER_SIZE };
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = PerformanceFront(client, form);
+ yield front.connect();
+
+ yield front.setProfilerStatusInterval(10);
+ let model = yield front.startRecording(config);
+ let stats = yield once(front, "profiler-status");
+ is(stats.totalSize, BUFFER_SIZE, `profiler-status event has totalSize: ${stats.totalSize}`);
+ ok(stats.position < BUFFER_SIZE, `profiler-status event has position: ${stats.position}`);
+ ok(stats.generation >= 0, `profiler-status event has generation: ${stats.generation}`);
+ ok(stats.isActive, "profiler-status event is isActive");
+ is(typeof stats.currentTime, "number", "profiler-status event has currentTime");
+
+ // Halt once more for a buffer status to ensure we're beyond 0
+ yield once(front, "profiler-status");
+
+ let lastBufferStatus = 0;
+ let checkCount = 0;
+ while (lastBufferStatus < 1) {
+ let currentBufferStatus = front.getBufferUsageForRecording(model);
+ ok(currentBufferStatus > lastBufferStatus, `buffer is more filled than before: ${currentBufferStatus} > ${lastBufferStatus}`);
+ lastBufferStatus = currentBufferStatus;
+ checkCount++;
+ yield once(front, "profiler-status");
+ }
+
+ ok(checkCount >= 1, "atleast 1 event were fired until the buffer was filled");
+ is(lastBufferStatus, 1, "buffer usage cannot surpass 100%");
+ yield front.stopRecording(model);
+
+ is(front.getBufferUsageForRecording(model), null, "buffer usage should be null when no longer recording.");
+
+ yield front.destroy();
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_perf-samples-01.js b/devtools/server/tests/browser/browser_perf-samples-01.js
new file mode 100644
index 000000000..f8f4bf393
--- /dev/null
+++ b/devtools/server/tests/browser/browser_perf-samples-01.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the retrieved profiler data samples are correctly filtered and
+ * normalized before passed to consumers.
+ */
+
+const WAIT_TIME = 1000; // ms
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = PerformanceFront(client, form);
+ yield front.connect();
+
+ // Perform the first recording...
+
+ let firstRecording = yield front.startRecording();
+ let firstRecordingStartTime = firstRecording._startTime;
+ info("Started profiling at: " + firstRecordingStartTime);
+
+ busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
+
+ yield front.stopRecording(firstRecording);
+
+ ok(firstRecording.getDuration() >= WAIT_TIME,
+ "The first recording duration is correct.");
+
+ // Perform the second recording...
+
+ let secondRecording = yield front.startRecording();
+ let secondRecordingStartTime = secondRecording._startTime;
+ info("Started profiling at: " + secondRecordingStartTime);
+
+ busyWait(WAIT_TIME); // allow the profiler module to sample more cpu activity
+
+ yield front.stopRecording(secondRecording);
+ let secondRecordingProfile = secondRecording.getProfile();
+ let secondRecordingSamples = secondRecordingProfile.threads[0].samples.data;
+
+ ok(secondRecording.getDuration() >= WAIT_TIME,
+ "The second recording duration is correct.");
+
+ const TIME_SLOT = secondRecordingProfile.threads[0].samples.schema.time;
+ ok(secondRecordingSamples[0][TIME_SLOT] < secondRecordingStartTime,
+ "The second recorded sample times were normalized.");
+ ok(secondRecordingSamples[0][TIME_SLOT] > 0,
+ "The second recorded sample times were normalized correctly.");
+ ok(!secondRecordingSamples.find(e => e[TIME_SLOT] + secondRecordingStartTime <= firstRecording.getDuration()),
+ "There should be no samples from the first recording in the second one, " +
+ "even though the total number of frames did not overflow.");
+
+ yield front.destroy();
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_perf-samples-02.js b/devtools/server/tests/browser/browser_perf-samples-02.js
new file mode 100644
index 000000000..c4d51230d
--- /dev/null
+++ b/devtools/server/tests/browser/browser_perf-samples-02.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if the retrieved profiler data samples always have a (root) node.
+ * If this ever changes, the |ThreadNode.prototype.insert| function in
+ * devtools/client/performance/modules/logic/tree-model.js will have to be changed.
+ */
+
+const WAIT_TIME = 1000; // ms
+
+const { PerformanceFront } = require("devtools/shared/fronts/performance");
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "doc_perf.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = PerformanceFront(client, form);
+ yield front.connect();
+
+ let rec = yield front.startRecording();
+ busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
+
+ yield front.stopRecording(rec);
+ let profile = rec.getProfile();
+ let sampleCount = 0;
+
+ for (let thread of profile.threads) {
+ info("Checking thread: " + thread.name);
+
+ for (let sample of thread.samples.data) {
+ sampleCount++;
+
+ let stack = getInflatedStackLocations(thread, sample);
+ if (stack[0] != "(root)") {
+ ok(false, "The sample " + stack.toSource() + " doesn't have a root node.");
+ }
+ }
+ }
+
+ ok(sampleCount > 0,
+ "At least some samples have been iterated over, checking for root nodes.");
+
+ yield front.destroy();
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * Inflate a particular sample's stack and return an array of strings.
+ */
+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/browser/browser_register_actor.js b/devtools/server/tests/browser/browser_register_actor.js
new file mode 100644
index 000000000..73ee0cedc
--- /dev/null
+++ b/devtools/server/tests/browser/browser_register_actor.js
@@ -0,0 +1,76 @@
+var gClient;
+
+function test() {
+ waitForExplicitFinish();
+ var {ActorRegistryFront} = require("devtools/shared/fronts/actor-registry");
+ var actorURL = "chrome://mochitests/content/chrome/devtools/server/tests/mochitest/hello-actor.js";
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ gClient = new DebuggerClient(DebuggerServer.connectPipe());
+ gClient.connect()
+ .then(() => gClient.listTabs())
+ .then(aResponse => {
+
+ var options = {
+ prefix: "helloActor",
+ constructor: "HelloActor",
+ type: { tab: true }
+ };
+
+ var registry = ActorRegistryFront(gClient, aResponse);
+ registry.registerActor(actorURL, options).then(actorFront => {
+ gClient.listTabs(response => {
+ var tab = response.tabs[response.selected];
+ ok(!!tab.helloActor, "Hello actor must exist");
+
+ // Make sure actor's state is maintained across listTabs requests.
+ checkActorState(tab.helloActor, () => {
+
+ // Clean up
+ actorFront.unregister().then(() => {
+ gClient.close().then(() => {
+ DebuggerServer.destroy();
+ gClient = null;
+ finish();
+ });
+ });
+ });
+ });
+ });
+ });
+}
+
+function checkActorState(helloActor, callback) {
+ getCount(helloActor, response => {
+ ok(!response.error, "No error");
+ is(response.count, 1, "The counter must be valid");
+
+ getCount(helloActor, response => {
+ ok(!response.error, "No error");
+ is(response.count, 2, "The counter must be valid");
+
+ gClient.listTabs(response => {
+ var tab = response.tabs[response.selected];
+ is(tab.helloActor, helloActor, "Hello actor must be valid");
+
+ getCount(helloActor, response => {
+ ok(!response.error, "No error");
+ is(response.count, 3, "The counter must be valid");
+
+ callback();
+ });
+ });
+ });
+ });
+}
+
+function getCount(actor, callback) {
+ gClient.request({
+ to: actor,
+ type: "count"
+ }, callback);
+}
diff --git a/devtools/server/tests/browser/browser_storage_dynamic_windows.js b/devtools/server/tests/browser/browser_storage_dynamic_windows.js
new file mode 100644
index 000000000..440c91222
--- /dev/null
+++ b/devtools/server/tests/browser/browser_storage_dynamic_windows.js
@@ -0,0 +1,294 @@
+/* vim: set 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";
+
+const {StorageFront} = require("devtools/shared/fronts/storage");
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/storage-helpers.js", this);
+
+const beforeReload = {
+ cookies: {
+ "test1.example.org": ["c1", "cs2", "c3", "uc1"],
+ "sectest1.example.org": ["uc1", "cs2"]
+ },
+ localStorage: {
+ "http://test1.example.org": ["ls1", "ls2"],
+ "http://sectest1.example.org": ["iframe-u-ls1"]
+ },
+ sessionStorage: {
+ "http://test1.example.org": ["ss1"],
+ "http://sectest1.example.org": ["iframe-u-ss1", "iframe-u-ss2"]
+ },
+ indexedDB: {
+ "http://test1.example.org": [
+ JSON.stringify(["idb1", "obj1"]),
+ JSON.stringify(["idb1", "obj2"]),
+ JSON.stringify(["idb2", "obj3"]),
+ ],
+ "http://sectest1.example.org": []
+ }
+};
+
+function* testStores(data, front) {
+ testWindowsBeforeReload(data);
+
+ // FIXME: Bug 1183581 - browser_storage_dynamic_windows.js IsSafeToRunScript
+ // errors when testing reload in E10S mode
+ // yield testReload(front);
+ yield testAddIframe(front);
+ yield testRemoveIframe(front);
+}
+
+function testWindowsBeforeReload(data) {
+ for (let storageType in beforeReload) {
+ ok(data[storageType], storageType + " storage actor is present");
+ is(Object.keys(data[storageType].hosts).length,
+ Object.keys(beforeReload[storageType]).length,
+ "Number of hosts for " + storageType + "match");
+ for (let host in beforeReload[storageType]) {
+ ok(data[storageType].hosts[host], "Host " + host + " is present");
+ }
+ }
+}
+
+function markOutMatched(toBeEmptied, data, deleted) {
+ if (!Object.keys(toBeEmptied).length) {
+ info("Object empty");
+ return;
+ }
+ ok(Object.keys(data).length,
+ "At least one storage type should be present");
+ for (let storageType in toBeEmptied) {
+ if (!data[storageType]) {
+ continue;
+ }
+ info("Testing for " + storageType);
+ for (let host in data[storageType]) {
+ ok(toBeEmptied[storageType][host], "Host " + host + " found");
+ if (!deleted) {
+ for (let item of data[storageType][host]) {
+ let index = toBeEmptied[storageType][host].indexOf(item);
+ ok(index > -1, "Item found - " + item);
+ if (index > -1) {
+ toBeEmptied[storageType][host].splice(index, 1);
+ }
+ }
+ if (!toBeEmptied[storageType][host].length) {
+ delete toBeEmptied[storageType][host];
+ }
+ } else {
+ delete toBeEmptied[storageType][host];
+ }
+ }
+ if (!Object.keys(toBeEmptied[storageType]).length) {
+ delete toBeEmptied[storageType];
+ }
+ }
+}
+
+// function testReload(front) {
+// info("Testing if reload works properly");
+
+// let shouldBeEmptyFirst = Cu.cloneInto(beforeReload, {});
+// let shouldBeEmptyLast = Cu.cloneInto(beforeReload, {});
+// return new Promise(resolve => {
+
+// let onStoresUpdate = data => {
+// info("in stores update of testReload");
+// // This might be second time stores update is happening, in which case,
+// // data.deleted will be null.
+// // OR.. This might be the first time on a super slow machine where both
+// // data.deleted and data.added is missing in the first update.
+// if (data.deleted) {
+// markOutMatched(shouldBeEmptyFirst, data.deleted, true);
+// }
+
+// if (!Object.keys(shouldBeEmptyFirst).length) {
+// info("shouldBeEmptyFirst is empty now");
+// }
+
+// // stores-update call might not have data.added for the first time on
+// // slow machines, in which case, data.added will be null
+// if (data.added) {
+// markOutMatched(shouldBeEmptyLast, data.added);
+// }
+
+// if (!Object.keys(shouldBeEmptyLast).length) {
+// info("Everything to be received is received.");
+// endTestReloaded();
+// }
+// };
+
+// let endTestReloaded = () => {
+// front.off("stores-update", onStoresUpdate);
+// resolve();
+// };
+
+// front.on("stores-update", onStoresUpdate);
+
+// content.location.reload();
+// });
+// }
+
+function testAddIframe(front) {
+ info("Testing if new iframe addition works properly");
+ return new Promise(resolve => {
+ let shouldBeEmpty = {
+ localStorage: {
+ "https://sectest1.example.org": ["iframe-s-ls1"]
+ },
+ sessionStorage: {
+ "https://sectest1.example.org": ["iframe-s-ss1"]
+ },
+ cookies: {
+ "sectest1.example.org": ["sc1"]
+ },
+ indexedDB: {
+ // empty because indexed db creation happens after the page load, so at
+ // the time of window-ready, there was no indexed db present.
+ "https://sectest1.example.org": []
+ },
+ Cache: {
+ "https://sectest1.example.org":[]
+ }
+ };
+
+ let onStoresUpdate = data => {
+ info("checking if the hosts list is correct for this iframe addition");
+
+ markOutMatched(shouldBeEmpty, data.added);
+
+ ok(!data.changed || !data.changed.cookies ||
+ !data.changed.cookies["https://sectest1.example.org"],
+ "Nothing got changed for cookies");
+ ok(!data.changed || !data.changed.localStorage ||
+ !data.changed.localStorage["https://sectest1.example.org"],
+ "Nothing got changed for local storage");
+ ok(!data.changed || !data.changed.sessionStorage ||
+ !data.changed.sessionStorage["https://sectest1.example.org"],
+ "Nothing got changed for session storage");
+ ok(!data.changed || !data.changed.indexedDB ||
+ !data.changed.indexedDB["https://sectest1.example.org"],
+ "Nothing got changed for indexed db");
+
+ ok(!data.deleted || !data.deleted.cookies ||
+ !data.deleted.cookies["https://sectest1.example.org"],
+ "Nothing got deleted for cookies");
+ ok(!data.deleted || !data.deleted.localStorage ||
+ !data.deleted.localStorage["https://sectest1.example.org"],
+ "Nothing got deleted for local storage");
+ ok(!data.deleted || !data.deleted.sessionStorage ||
+ !data.deleted.sessionStorage["https://sectest1.example.org"],
+ "Nothing got deleted for session storage");
+ ok(!data.deleted || !data.deleted.indexedDB ||
+ !data.deleted.indexedDB["https://sectest1.example.org"],
+ "Nothing got deleted for indexed db");
+
+ if (!Object.keys(shouldBeEmpty).length) {
+ info("Everything to be received is received.");
+ endTestReloaded();
+ }
+ };
+
+ let endTestReloaded = () => {
+ front.off("stores-update", onStoresUpdate);
+ resolve();
+ };
+
+ front.on("stores-update", onStoresUpdate);
+
+ let iframe = content.document.createElement("iframe");
+ iframe.src = ALT_DOMAIN_SECURED + "storage-secured-iframe.html";
+ content.document.querySelector("body").appendChild(iframe);
+ });
+}
+
+function testRemoveIframe(front) {
+ info("Testing if iframe removal works properly");
+ return new Promise(resolve => {
+ let shouldBeEmpty = {
+ localStorage: {
+ "http://sectest1.example.org": []
+ },
+ sessionStorage: {
+ "http://sectest1.example.org": []
+ },
+ Cache: {
+ "http://sectest1.example.org": []
+ },
+ indexedDB: {
+ "http://sectest1.example.org": []
+ }
+ };
+
+ let onStoresUpdate = data => {
+ info("checking if the hosts list is correct for this iframe deletion");
+
+ markOutMatched(shouldBeEmpty, data.deleted, true);
+
+ ok(!data.deleted.cookies || !data.deleted.cookies["sectest1.example.org"],
+ "Nothing got deleted for Cookies as " +
+ "the same hostname is still present");
+
+ ok(!data.changed || !data.changed.cookies ||
+ !data.changed.cookies["http://sectest1.example.org"],
+ "Nothing got changed for cookies");
+ ok(!data.changed || !data.changed.localStorage ||
+ !data.changed.localStorage["http://sectest1.example.org"],
+ "Nothing got changed for local storage");
+ ok(!data.changed || !data.changed.sessionStorage ||
+ !data.changed.sessionStorage["http://sectest1.example.org"],
+ "Nothing got changed for session storage");
+
+ ok(!data.added || !data.added.cookies ||
+ !data.added.cookies["http://sectest1.example.org"],
+ "Nothing got added for cookies");
+ ok(!data.added || !data.added.localStorage ||
+ !data.added.localStorage["http://sectest1.example.org"],
+ "Nothing got added for local storage");
+ ok(!data.added || !data.added.sessionStorage ||
+ !data.added.sessionStorage["http://sectest1.example.org"],
+ "Nothing got added for session storage");
+
+ if (!Object.keys(shouldBeEmpty).length) {
+ info("Everything to be received is received.");
+ endTestReloaded();
+ }
+ };
+
+ let endTestReloaded = () => {
+ front.off("stores-update", onStoresUpdate);
+ resolve();
+ };
+
+ front.on("stores-update", onStoresUpdate);
+
+ for (let iframe of content.document.querySelectorAll("iframe")) {
+ if (iframe.src.startsWith("http:")) {
+ iframe.remove();
+ break;
+ }
+ }
+ });
+}
+
+add_task(function* () {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-dynamic-windows.html");
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = StorageFront(client, form);
+ let data = yield front.listStores();
+ yield testStores(data, front);
+
+ yield clearStorage();
+
+ // Forcing GC/CC to get rid of docshells and windows created by this test.
+ forceCollections();
+ yield client.close();
+ forceCollections();
+ DebuggerServer.destroy();
+ forceCollections();
+});
diff --git a/devtools/server/tests/browser/browser_storage_listings.js b/devtools/server/tests/browser/browser_storage_listings.js
new file mode 100644
index 000000000..4ff3c3fc1
--- /dev/null
+++ b/devtools/server/tests/browser/browser_storage_listings.js
@@ -0,0 +1,610 @@
+/* vim: set 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";
+
+const {StorageFront} = require("devtools/shared/fronts/storage");
+Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/server/tests/browser/storage-helpers.js", this);
+
+const storeMap = {
+ cookies: {
+ "test1.example.org": [
+ {
+ name: "c1",
+ value: "foobar",
+ expires: 2000000000000,
+ path: "/browser",
+ host: "test1.example.org",
+ isDomain: false,
+ isSecure: false,
+ },
+ {
+ name: "cs2",
+ value: "sessionCookie",
+ path: "/",
+ host: ".example.org",
+ expires: 0,
+ isDomain: true,
+ isSecure: false,
+ },
+ {
+ name: "c3",
+ value: "foobar-2",
+ expires: 2000000001000,
+ path: "/",
+ host: "test1.example.org",
+ isDomain: false,
+ isSecure: false,
+ },
+ {
+ name: "uc1",
+ value: "foobar",
+ host: ".example.org",
+ path: "/",
+ expires: 0,
+ isDomain: true,
+ isSecure: true,
+ }
+ ],
+ "sectest1.example.org": [
+ {
+ name: "uc1",
+ value: "foobar",
+ host: ".example.org",
+ path: "/",
+ expires: 0,
+ isDomain: true,
+ isSecure: true,
+ },
+ {
+ name: "cs2",
+ value: "sessionCookie",
+ path: "/",
+ host: ".example.org",
+ expires: 0,
+ isDomain: true,
+ isSecure: false,
+ },
+ {
+ name: "sc1",
+ value: "foobar",
+ path: "/browser/devtools/server/tests/browser/",
+ host: "sectest1.example.org",
+ expires: 0,
+ isDomain: false,
+ isSecure: false,
+ }
+ ]
+ },
+ localStorage: {
+ "http://test1.example.org": [
+ {
+ name: "ls1",
+ value: "foobar"
+ },
+ {
+ name: "ls2",
+ value: "foobar-2"
+ }
+ ],
+ "http://sectest1.example.org": [
+ {
+ name: "iframe-u-ls1",
+ value: "foobar"
+ }
+ ],
+ "https://sectest1.example.org": [
+ {
+ name: "iframe-s-ls1",
+ value: "foobar"
+ }
+ ]
+ },
+ sessionStorage: {
+ "http://test1.example.org": [
+ {
+ name: "ss1",
+ value: "foobar-3"
+ }
+ ],
+ "http://sectest1.example.org": [
+ {
+ name: "iframe-u-ss1",
+ value: "foobar1"
+ },
+ {
+ name: "iframe-u-ss2",
+ value: "foobar2"
+ }
+ ],
+ "https://sectest1.example.org": [
+ {
+ name: "iframe-s-ss1",
+ value: "foobar-2"
+ }
+ ]
+ }
+};
+
+const IDBValues = {
+ listStoresResponse: {
+ "http://test1.example.org": [
+ ["idb1", "obj1"], ["idb1", "obj2"], ["idb2", "obj3"]
+ ],
+ "http://sectest1.example.org": [
+ ],
+ "https://sectest1.example.org": [
+ ["idb-s1", "obj-s1"], ["idb-s2", "obj-s2"]
+ ]
+ },
+ dbDetails : {
+ "http://test1.example.org": [
+ {
+ db: "idb1",
+ origin: "http://test1.example.org",
+ version: 1,
+ objectStores: 2
+ },
+ {
+ db: "idb2",
+ origin: "http://test1.example.org",
+ version: 1,
+ objectStores: 1
+ },
+ ],
+ "http://sectest1.example.org": [
+ ],
+ "https://sectest1.example.org": [
+ {
+ db: "idb-s1",
+ origin: "https://sectest1.example.org",
+ version: 1,
+ objectStores: 1
+ },
+ {
+ db: "idb-s2",
+ origin: "https://sectest1.example.org",
+ version: 1,
+ objectStores: 1
+ },
+ ]
+ },
+ objectStoreDetails: {
+ "http://test1.example.org": {
+ idb1: [
+ {
+ objectStore: "obj1",
+ keyPath: "id",
+ autoIncrement: false,
+ indexes: [
+ {
+ name: "name",
+ keyPath: "name",
+ "unique": false,
+ multiEntry: false,
+ },
+ {
+ name: "email",
+ keyPath: "email",
+ "unique": true,
+ multiEntry: false,
+ },
+ ]
+ },
+ {
+ objectStore: "obj2",
+ keyPath: "id2",
+ autoIncrement: false,
+ indexes: []
+ }
+ ],
+ idb2: [
+ {
+ objectStore: "obj3",
+ keyPath: "id3",
+ autoIncrement: false,
+ indexes: [
+ {
+ name: "name2",
+ keyPath: "name2",
+ "unique": true,
+ multiEntry: false,
+ }
+ ]
+ },
+ ]
+ },
+ "http://sectest1.example.org" : {},
+ "https://sectest1.example.org": {
+ "idb-s1": [
+ {
+ objectStore: "obj-s1",
+ keyPath: "id",
+ autoIncrement: false,
+ indexes: []
+ },
+ ],
+ "idb-s2": [
+ {
+ objectStore: "obj-s2",
+ keyPath: "id3",
+ autoIncrement: true,
+ indexes: [
+ {
+ name: "name2",
+ keyPath: "name2",
+ "unique": true,
+ multiEntry: false,
+ }
+ ]
+ },
+ ]
+ }
+
+ },
+ entries: {
+ "http://test1.example.org": {
+ "idb1#obj1": [
+ {
+ name: 1,
+ value: {
+ id: 1,
+ name: "foo",
+ email: "foo@bar.com",
+ }
+ },
+ {
+ name: 2,
+ value: {
+ id: 2,
+ name: "foo2",
+ email: "foo2@bar.com",
+ }
+ },
+ {
+ name: 3,
+ value: {
+ id: 3,
+ name: "foo2",
+ email: "foo3@bar.com",
+ }
+ }
+ ],
+ "idb1#obj2": [
+ {
+ name: 1,
+ value: {
+ id2: 1,
+ name: "foo",
+ email: "foo@bar.com",
+ extra: "baz"
+ }
+ }
+ ],
+ "idb2#obj3": []
+ },
+ "http://sectest1.example.org" : {},
+ "https://sectest1.example.org": {
+ "idb-s1#obj-s1": [
+ {
+ name: 6,
+ value: {
+ id: 6,
+ name: "foo",
+ email: "foo@bar.com",
+ }
+ },
+ {
+ name: 7,
+ value: {
+ id: 7,
+ name: "foo2",
+ email: "foo2@bar.com",
+ }
+ }
+ ],
+ "idb-s2#obj-s2": [
+ {
+ name: 13,
+ value: {
+ id2: 13,
+ name2: "foo",
+ email: "foo@bar.com",
+ }
+ }
+ ]
+ }
+ }
+};
+
+function finishTests(client) {
+
+ let closeConnection = () => {
+
+ };
+}
+
+function* testStores(data) {
+ ok(data.cookies, "Cookies storage actor is present");
+ ok(data.localStorage, "Local Storage storage actor is present");
+ ok(data.sessionStorage, "Session Storage storage actor is present");
+ ok(data.indexedDB, "Indexed DB storage actor is present");
+ yield testCookies(data.cookies);
+ yield testLocalStorage(data.localStorage);
+ yield testSessionStorage(data.sessionStorage);
+ yield testIndexedDB(data.indexedDB);
+}
+
+function testCookies(cookiesActor) {
+ is(Object.keys(cookiesActor.hosts).length, 2, "Correct number of host entries for cookies");
+ return testCookiesObjects(0, cookiesActor.hosts, cookiesActor);
+}
+
+var testCookiesObjects = Task.async(function* (index, hosts, cookiesActor) {
+ let host = Object.keys(hosts)[index];
+ let matchItems = data => {
+ let cookiesLength = 0;
+ for (let secureCookie of storeMap.cookies[host]) {
+ if (secureCookie.isSecure) {
+ ++cookiesLength;
+ }
+ }
+ // Any secure cookies did not get stored in the database.
+ is(data.total, storeMap.cookies[host].length - cookiesLength,
+ "Number of cookies in host " + host + " matches");
+ for (let item of data.data) {
+ let found = false;
+ for (let toMatch of storeMap.cookies[host]) {
+ if (item.name == toMatch.name) {
+ found = true;
+ ok(true, "Found cookie " + item.name + " in response");
+ is(item.value.str, toMatch.value, "The value matches.");
+ is(item.expires, toMatch.expires, "The expiry time matches.");
+ is(item.path, toMatch.path, "The path matches.");
+ is(item.host, toMatch.host, "The host matches.");
+ is(item.isSecure, toMatch.isSecure, "The isSecure value matches.");
+ is(item.isDomain, toMatch.isDomain, "The isDomain value matches.");
+ break;
+ }
+ }
+ ok(found, "cookie " + item.name + " should exist in response");
+ }
+ };
+
+ ok(!!storeMap.cookies[host], "Host is present in the list : " + host);
+ matchItems(yield cookiesActor.getStoreObjects(host));
+ if (index == Object.keys(hosts).length - 1) {
+ return;
+ }
+ yield testCookiesObjects(++index, hosts, cookiesActor);
+});
+
+function testLocalStorage(localStorageActor) {
+ is(Object.keys(localStorageActor.hosts).length, 3,
+ "Correct number of host entries for local storage");
+ return testLocalStorageObjects(0, localStorageActor.hosts, localStorageActor);
+}
+
+var testLocalStorageObjects = Task.async(function* (index, hosts, localStorageActor) {
+ let host = Object.keys(hosts)[index];
+ let matchItems = data => {
+ is(data.total, storeMap.localStorage[host].length,
+ "Number of local storage items in host " + host + " matches");
+ for (let item of data.data) {
+ let found = false;
+ for (let toMatch of storeMap.localStorage[host]) {
+ if (item.name == toMatch.name) {
+ found = true;
+ ok(true, "Found local storage item " + item.name + " in response");
+ is(item.value.str, toMatch.value, "The value matches.");
+ break;
+ }
+ }
+ ok(found, "local storage item " + item.name + " should exist in response");
+ }
+ };
+
+ ok(!!storeMap.localStorage[host], "Host is present in the list : " + host);
+ matchItems(yield localStorageActor.getStoreObjects(host));
+ if (index == Object.keys(hosts).length - 1) {
+ return;
+ }
+ yield testLocalStorageObjects(++index, hosts, localStorageActor);
+});
+
+function testSessionStorage(sessionStorageActor) {
+ is(Object.keys(sessionStorageActor.hosts).length, 3,
+ "Correct number of host entries for session storage");
+ return testSessionStorageObjects(0, sessionStorageActor.hosts,
+ sessionStorageActor);
+}
+
+var testSessionStorageObjects = Task.async(function* (index, hosts, sessionStorageActor) {
+ let host = Object.keys(hosts)[index];
+ let matchItems = data => {
+ is(data.total, storeMap.sessionStorage[host].length,
+ "Number of session storage items in host " + host + " matches");
+ for (let item of data.data) {
+ let found = false;
+ for (let toMatch of storeMap.sessionStorage[host]) {
+ if (item.name == toMatch.name) {
+ found = true;
+ ok(true, "Found session storage item " + item.name + " in response");
+ is(item.value.str, toMatch.value, "The value matches.");
+ break;
+ }
+ }
+ ok(found, "session storage item " + item.name + " should exist in response");
+ }
+ };
+
+ ok(!!storeMap.sessionStorage[host], "Host is present in the list : " + host);
+ matchItems(yield sessionStorageActor.getStoreObjects(host));
+ if (index == Object.keys(hosts).length - 1) {
+ return;
+ }
+ yield testSessionStorageObjects(++index, hosts, sessionStorageActor);
+});
+
+var testIndexedDB = Task.async(function* (indexedDBActor) {
+ is(Object.keys(indexedDBActor.hosts).length, 3,
+ "Correct number of host entries for indexed db");
+
+ for (let host in indexedDBActor.hosts) {
+ for (let item of indexedDBActor.hosts[host]) {
+ let parsedItem = JSON.parse(item);
+ let found = false;
+ for (let toMatch of IDBValues.listStoresResponse[host]) {
+ if (toMatch[0] == parsedItem[0] && toMatch[1] == parsedItem[1]) {
+ found = true;
+ break;
+ }
+ }
+ ok(found, item + " should exist in list stores response");
+ }
+ }
+
+ yield testIndexedDBs(0, indexedDBActor.hosts, indexedDBActor);
+ yield testObjectStores(0, indexedDBActor.hosts, indexedDBActor);
+ yield testIDBEntries(0, indexedDBActor.hosts, indexedDBActor);
+});
+
+var testIndexedDBs = Task.async(function* (index, hosts, indexedDBActor) {
+ let host = Object.keys(hosts)[index];
+ let matchItems = data => {
+ is(data.total, IDBValues.dbDetails[host].length,
+ "Number of indexed db in host " + host + " matches");
+ for (let item of data.data) {
+ let found = false;
+ for (let toMatch of IDBValues.dbDetails[host]) {
+ if (item.db == toMatch.db) {
+ found = true;
+ ok(true, "Found indexed db " + item.db + " in response");
+ is(item.origin, toMatch.origin, "The origin matches.");
+ is(item.version, toMatch.version, "The version matches.");
+ is(item.objectStores, toMatch.objectStores,
+ "The numebr of object stores matches.");
+ break;
+ }
+ }
+ ok(found, "indexed db " + item.name + " should exist in response");
+ }
+ };
+
+ ok(!!IDBValues.dbDetails[host], "Host is present in the list : " + host);
+ matchItems(yield indexedDBActor.getStoreObjects(host));
+ if (index == Object.keys(hosts).length - 1) {
+ return;
+ }
+ yield testIndexedDBs(++index, hosts, indexedDBActor);
+});
+
+var testObjectStores = Task.async(function* (index, hosts, indexedDBActor) {
+ let host = Object.keys(hosts)[index];
+ let matchItems = (data, db) => {
+ is(data.total, IDBValues.objectStoreDetails[host][db].length,
+ "Number of object stores in host " + host + " matches");
+ for (let item of data.data) {
+ let found = false;
+ for (let toMatch of IDBValues.objectStoreDetails[host][db]) {
+ if (item.objectStore == toMatch.objectStore) {
+ found = true;
+ ok(true, "Found object store " + item.objectStore + " in response");
+ is(item.keyPath, toMatch.keyPath, "The keyPath matches.");
+ is(item.autoIncrement, toMatch.autoIncrement, "The autoIncrement matches.");
+ item.indexes = JSON.parse(item.indexes);
+ is(item.indexes.length, toMatch.indexes.length, "Number of indexes match");
+ for (let index of item.indexes) {
+ let indexFound = false;
+ for (let toMatchIndex of toMatch.indexes) {
+ if (toMatchIndex.name == index.name) {
+ indexFound = true;
+ ok(true, "Found index " + index.name);
+ is(index.keyPath, toMatchIndex.keyPath,
+ "The keyPath of index matches.");
+ is(index.unique, toMatchIndex.unique, "The unique matches");
+ is(index.multiEntry, toMatchIndex.multiEntry,
+ "The multiEntry matches");
+ break;
+ }
+ }
+ ok(indexFound, "Index " + index + " should exist in response");
+ }
+ break;
+ }
+ }
+ ok(found, "indexed db " + item.name + " should exist in response");
+ }
+ };
+
+ ok(!!IDBValues.objectStoreDetails[host], "Host is present in the list : " + host);
+ for (let name of hosts[host]) {
+ let objName = JSON.parse(name).slice(0, 1);
+ matchItems((
+ yield indexedDBActor.getStoreObjects(host, [JSON.stringify(objName)])
+ ), objName[0]);
+ }
+ if (index == Object.keys(hosts).length - 1) {
+ return;
+ }
+ yield testObjectStores(++index, hosts, indexedDBActor);
+});
+
+var testIDBEntries = Task.async(function* (index, hosts, indexedDBActor) {
+ let host = Object.keys(hosts)[index];
+ let matchItems = (data, obj) => {
+ is(data.total, IDBValues.entries[host][obj].length,
+ "Number of items in object store " + obj + " matches");
+ for (let item of data.data) {
+ let found = false;
+ for (let toMatch of IDBValues.entries[host][obj]) {
+ if (item.name == toMatch.name) {
+ found = true;
+ ok(true, "Found indexed db item " + item.name + " in response");
+ let value = JSON.parse(item.value.str);
+ is(Object.keys(value).length, Object.keys(toMatch.value).length,
+ "Number of entries in the value matches");
+ for (let key in value) {
+ is(value[key], toMatch.value[key],
+ "value of " + key + " value key matches");
+ }
+ break;
+ }
+ }
+ ok(found, "indexed db item " + item.name + " should exist in response");
+ }
+ };
+
+ ok(!!IDBValues.entries[host], "Host is present in the list : " + host);
+ for (let name of hosts[host]) {
+ let parsed = JSON.parse(name);
+ matchItems((
+ yield indexedDBActor.getStoreObjects(host, [name])
+ ), parsed[0] + "#" + parsed[1]);
+ }
+ if (index == Object.keys(hosts).length - 1) {
+ return;
+ }
+ yield testObjectStores(++index, hosts, indexedDBActor);
+});
+
+add_task(function* () {
+ yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-listings.html");
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = StorageFront(client, form);
+ let data = yield front.listStores();
+ yield testStores(data);
+
+ yield clearStorage();
+
+ // Forcing GC/CC to get rid of docshells and windows created by this test.
+ forceCollections();
+ yield client.close();
+ forceCollections();
+ DebuggerServer.destroy();
+ forceCollections();
+});
diff --git a/devtools/server/tests/browser/browser_storage_updates.js b/devtools/server/tests/browser/browser_storage_updates.js
new file mode 100644
index 000000000..28b2e509f
--- /dev/null
+++ b/devtools/server/tests/browser/browser_storage_updates.js
@@ -0,0 +1,304 @@
+/* vim: set 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";
+
+const {StorageFront} = require("devtools/shared/fronts/storage");
+const beforeReload = {
+ cookies: ["test1.example.org", "sectest1.example.org"],
+ localStorage: ["http://test1.example.org", "http://sectest1.example.org"],
+ sessionStorage: ["http://test1.example.org", "http://sectest1.example.org"],
+};
+
+const TESTS = [
+ // index 0
+ {
+ action: function (win) {
+ info('win.addCookie("c1", "foobar1")');
+ win.addCookie("c1", "foobar1");
+
+ info('win.addCookie("c2", "foobar2")');
+ win.addCookie("c2", "foobar2");
+
+ info('win.localStorage.setItem("l1", "foobar1")');
+ win.localStorage.setItem("l1", "foobar1");
+ },
+ expected: {
+ added: {
+ cookies: {
+ "test1.example.org": ["c1", "c2"]
+ },
+ localStorage: {
+ "http://test1.example.org": ["l1"]
+ }
+ }
+ }
+ },
+
+ // index 1
+ {
+ action: function (win) {
+ info('win.addCookie("c1", "new_foobar1")');
+ win.addCookie("c1", "new_foobar1");
+
+ info('win.localStorage.setItem("l2", "foobar2")');
+ win.localStorage.setItem("l2", "foobar2");
+ },
+ expected: {
+ changed: {
+ cookies: {
+ "test1.example.org": ["c1"]
+ }
+ },
+ added: {
+ localStorage: {
+ "http://test1.example.org": ["l2"]
+ }
+ }
+ }
+ },
+
+ // index 2
+ {
+ action: function (win) {
+ info('win.removeCookie("c2")');
+ win.removeCookie("c2");
+
+ info('win.localStorage.removeItem("l1")');
+ win.localStorage.removeItem("l1");
+
+ info('win.localStorage.setItem("l3", "foobar3")');
+ win.localStorage.setItem("l3", "foobar3");
+ },
+ expected: {
+ deleted: {
+ cookies: {
+ "test1.example.org": ["c2"]
+ },
+ localStorage: {
+ "http://test1.example.org": ["l1"]
+ }
+ },
+ added: {
+ localStorage: {
+ "http://test1.example.org": ["l3"]
+ }
+ }
+ }
+ },
+
+ // index 3
+ {
+ action: function (win) {
+ info('win.removeCookie("c1")');
+ win.removeCookie("c1");
+
+ info('win.addCookie("c3", "foobar3")');
+ win.addCookie("c3", "foobar3");
+
+ info('win.localStorage.removeItem("l2")');
+ win.localStorage.removeItem("l2");
+
+ info('win.sessionStorage.setItem("s1", "foobar1")');
+ win.sessionStorage.setItem("s1", "foobar1");
+
+ info('win.sessionStorage.setItem("s2", "foobar2")');
+ win.sessionStorage.setItem("s2", "foobar2");
+
+ info('win.localStorage.setItem("l3", "new_foobar3")');
+ win.localStorage.setItem("l3", "new_foobar3");
+ },
+ expected: {
+ added: {
+ cookies: {
+ "test1.example.org": ["c3"]
+ },
+ sessionStorage: {
+ "http://test1.example.org": ["s1", "s2"]
+ }
+ },
+ changed: {
+ localStorage: {
+ "http://test1.example.org": ["l3"]
+ }
+ },
+ deleted: {
+ cookies: {
+ "test1.example.org": ["c1"]
+ },
+ localStorage: {
+ "http://test1.example.org": ["l2"]
+ }
+ }
+ }
+ },
+
+ // index 4
+ {
+ action: function (win) {
+ info('win.sessionStorage.removeItem("s1")');
+ win.sessionStorage.removeItem("s1");
+ },
+ expected: {
+ deleted: {
+ sessionStorage: {
+ "http://test1.example.org": ["s1"]
+ }
+ }
+ }
+ },
+
+ // index 5
+ {
+ action: function (win) {
+ info("win.clearCookies()");
+ win.clearCookies();
+ },
+ expected: {
+ deleted: {
+ cookies: {
+ "test1.example.org": ["c3"]
+ }
+ }
+ }
+ }
+];
+
+function markOutMatched(toBeEmptied, data) {
+ if (!Object.keys(toBeEmptied).length) {
+ info("Object empty");
+ return;
+ }
+ ok(Object.keys(data).length, "At least one storage type should be present");
+
+ for (let storageType in toBeEmptied) {
+ if (!data[storageType]) {
+ continue;
+ }
+ info("Testing for " + storageType);
+ for (let host in data[storageType]) {
+ ok(toBeEmptied[storageType][host], "Host " + host + " found");
+
+ for (let item of data[storageType][host]) {
+ let index = toBeEmptied[storageType][host].indexOf(item);
+ ok(index > -1, "Item found - " + item);
+ if (index > -1) {
+ toBeEmptied[storageType][host].splice(index, 1);
+ }
+ }
+ if (!toBeEmptied[storageType][host].length) {
+ delete toBeEmptied[storageType][host];
+ }
+ }
+ if (!Object.keys(toBeEmptied[storageType]).length) {
+ delete toBeEmptied[storageType];
+ }
+ }
+}
+
+function onStoresUpdate(expected, {added, changed, deleted}, index) {
+ info("inside stores update for index " + index);
+
+ // Here, added, changed and deleted might be null even if they are required as
+ // per expected. This is fine as they might come in the next stores-update
+ // call or have already come in the previous one.
+ if (added) {
+ info("matching added object for index " + index);
+ markOutMatched(expected.added, added);
+ }
+ if (changed) {
+ info("matching changed object for index " + index);
+ markOutMatched(expected.changed, changed);
+ }
+ if (deleted) {
+ info("matching deleted object for index " + index);
+ markOutMatched(expected.deleted, deleted);
+ }
+
+ if ((!expected.added || !Object.keys(expected.added).length) &&
+ (!expected.changed || !Object.keys(expected.changed).length) &&
+ (!expected.deleted || !Object.keys(expected.deleted).length)) {
+ info("Everything expected has been received for index " + index);
+ } else {
+ info("Still some updates pending for index " + index);
+ }
+}
+
+function runTest({action, expected}, front, win, index) {
+ return new Promise(resolve => {
+ front.once("stores-update", function (addedChangedDeleted) {
+ onStoresUpdate(expected, addedChangedDeleted, index);
+ resolve();
+ });
+
+ info("Running test at index " + index);
+ action(win);
+ });
+}
+
+function* testClearLocalAndSessionStores(front, win) {
+ return new Promise(resolve => {
+ // We need to wait until we have received stores-cleared for both local and
+ // session storage.
+ let localStorage = false;
+ let sessionStorage = false;
+
+ front.on("stores-cleared", function onStoresCleared(data) {
+ storesCleared(data);
+
+ if (data.localStorage) {
+ localStorage = true;
+ }
+ if (data.sessionStorage) {
+ sessionStorage = true;
+ }
+ if (localStorage && sessionStorage) {
+ front.off("stores-cleared", onStoresCleared);
+ resolve();
+ }
+ });
+
+ win.clearLocalAndSessionStores();
+ });
+}
+
+function storesCleared(data) {
+ if (data.sessionStorage || data.localStorage) {
+ let hosts = data.sessionStorage || data.localStorage;
+ info("Stores cleared required for session storage");
+ is(hosts.length, 1, "number of hosts is 1");
+ is(hosts[0], "http://test1.example.org",
+ "host matches for " + Object.keys(data)[0]);
+ } else {
+ ok(false, "Stores cleared should only be for local and session storage");
+ }
+}
+
+function* finishTests(client) {
+ yield client.close();
+ DebuggerServer.destroy();
+ finish();
+}
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "storage-updates.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = StorageFront(client, form);
+ let win = doc.defaultView.wrappedJSObject;
+
+ yield front.listStores();
+
+ for (let i = 0; i < TESTS.length; i++) {
+ let test = TESTS[i];
+ yield runTest(test, front, win, i);
+ }
+
+ yield testClearLocalAndSessionStores(front, win);
+ yield finishTests(client);
+});
diff --git a/devtools/server/tests/browser/browser_stylesheets_getTextEmpty.js b/devtools/server/tests/browser/browser_stylesheets_getTextEmpty.js
new file mode 100644
index 000000000..f7bb7057e
--- /dev/null
+++ b/devtools/server/tests/browser/browser_stylesheets_getTextEmpty.js
@@ -0,0 +1,40 @@
+/* vim: set 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 StyleSheetActor.getText handles empty text correctly.
+
+const {StyleSheetsFront} = require("devtools/shared/fronts/stylesheets");
+
+const CONTENT = "<style>body { background-color: #f0c; }</style>";
+const TEST_URI = "data:text/html;charset=utf-8," + encodeURIComponent(CONTENT);
+
+add_task(function* () {
+ yield addTab(TEST_URI);
+
+ info("Initialising the debugger server and client.");
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+
+ info("Attaching to the active tab.");
+ yield client.attachTab(form.actor);
+
+ let front = StyleSheetsFront(client, form);
+ ok(front, "The StyleSheetsFront was created.");
+
+ let sheets = yield front.getStyleSheets();
+ ok(sheets, "getStyleSheets() succeeded");
+ is(sheets.length, 1,
+ "getStyleSheets() returned the correct number of sheets");
+
+ let sheet = sheets[0];
+ yield sheet.update("", false);
+ let longStr = yield sheet.getText();
+ let source = yield longStr.string();
+ is(source, "", "text is empty");
+
+ yield client.close();
+});
diff --git a/devtools/server/tests/browser/browser_stylesheets_nested-iframes.js b/devtools/server/tests/browser/browser_stylesheets_nested-iframes.js
new file mode 100644
index 000000000..c382b6ce8
--- /dev/null
+++ b/devtools/server/tests/browser/browser_stylesheets_nested-iframes.js
@@ -0,0 +1,38 @@
+/* vim: set 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 StyleSheetsActor.getStyleSheets() works if an iframe does not have
+// a content document.
+
+const {StyleSheetsFront} = require("devtools/shared/fronts/stylesheets");
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "stylesheets-nested-iframes.html");
+ let doc = browser.contentDocument;
+
+ info("Initialising the debugger server and client.");
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+
+ info("Attaching to the active tab.");
+ yield client.attachTab(form.actor);
+
+ let front = StyleSheetsFront(client, form);
+ ok(front, "The StyleSheetsFront was created.");
+
+ let sheets = yield front.getStyleSheets();
+ ok(sheets, "getStyleSheets() succeeded even with documentless iframes.");
+
+ // Bug 285395 limits the number of nested iframes to 10. There's one sheet per
+ // frame so we should get 10 sheets. However, the limit might change in the
+ // future so it's better not to rely on the limit. Asserting > 2 ensures that
+ // the test page is actually loading nested iframes and this test is doing
+ // something sensible (if we got this far, the test has served its purpose).
+ ok(sheets.length > 2, sheets.length + " sheets found (expected 3 or more).");
+
+ yield client.close();
+});
diff --git a/devtools/server/tests/browser/browser_timeline.js b/devtools/server/tests/browser/browser_timeline.js
new file mode 100644
index 000000000..1e5793447
--- /dev/null
+++ b/devtools/server/tests/browser/browser_timeline.js
@@ -0,0 +1,63 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the timeline front's start/stop/isRecording methods work in a
+// simple use case, and that markers events are sent when operations occur.
+// Note that this test isn't concerned with which markers are actually recorded,
+// just that markers are recorded at all.
+// Trying to check marker types here may lead to intermittents, see bug 1066474.
+
+const {TimelineFront} = require("devtools/shared/fronts/timeline");
+
+add_task(function* () {
+ let browser = yield addTab("data:text/html;charset=utf-8,mop");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = TimelineFront(client, form);
+
+ ok(front, "The TimelineFront was created");
+
+ let isActive = yield front.isRecording();
+ ok(!isActive, "The TimelineFront is not initially recording");
+
+ info("Flush any pending reflows");
+ let forceSyncReflow = doc.body.innerHeight;
+
+ info("Start recording");
+ yield front.start({ withMarkers: true });
+
+ isActive = yield front.isRecording();
+ ok(isActive, "The TimelineFront is now recording");
+
+ info("Change some style on the page to cause style/reflow/paint");
+ let onMarkers = once(front, "markers");
+ doc.body.style.padding = "10px";
+ let markers = yield onMarkers;
+
+ ok(true, "The markers event was fired");
+ ok(markers.length > 0, "Markers were returned");
+
+ info("Flush pending reflows again");
+ forceSyncReflow = doc.body.innerHeight;
+
+ info("Change some style on the page to cause style/paint");
+ onMarkers = once(front, "markers");
+ doc.body.style.backgroundColor = "red";
+ markers = yield onMarkers;
+
+ ok(markers.length > 0, "markers were returned");
+
+ yield front.stop();
+
+ isActive = yield front.isRecording();
+ ok(!isActive, "Not recording after stop()");
+
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
diff --git a/devtools/server/tests/browser/browser_timeline_actors.js b/devtools/server/tests/browser/browser_timeline_actors.js
new file mode 100644
index 000000000..a902775fa
--- /dev/null
+++ b/devtools/server/tests/browser/browser_timeline_actors.js
@@ -0,0 +1,69 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the timeline can also record data from the memory and framerate
+// actors, emitted as events in tadem with the markers.
+
+const {TimelineFront} = require("devtools/shared/fronts/timeline");
+
+add_task(function* () {
+ let browser = yield addTab("data:text/html;charset=utf-8,mop");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = TimelineFront(client, form);
+
+ info("Start timeline marker recording");
+ yield front.start({ withMemory: true, withTicks: true });
+
+ let updatedMemory = 0;
+ let updatedTicks = 0;
+
+ front.on("memory", (delta, measurement) => {
+ ok(delta > 0, "The delta should be a timestamp.");
+ ok(measurement, "The measurement should not be null.");
+ ok(measurement.total > 0, "There should be a 'total' value in the measurement.");
+ info("Received 'memory' event at " + delta + " with " + measurement.toSource());
+ updatedMemory++;
+ });
+
+ front.on("ticks", (delta, ticks) => {
+ ok(delta > 0, "The delta should be a timestamp.");
+ ok(ticks, "The ticks should not be null.");
+ info("Received 'ticks' event with " + ticks.toSource());
+ updatedTicks++;
+ });
+
+ ok((yield waitUntil(() => updatedMemory > 1)),
+ "Some memory measurements were emitted.");
+ ok((yield waitUntil(() => updatedTicks > 1)),
+ "Some refresh driver ticks were emitted.");
+
+ info("Stop timeline marker recording");
+ yield front.stop();
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
+
+/**
+ * Waits until a predicate returns true.
+ *
+ * @param function predicate
+ * Invoked once in a while until it returns true.
+ * @param number interval [optional]
+ * How often the predicate is invoked, in milliseconds.
+ */
+function waitUntil(predicate, interval = 10) {
+ if (predicate()) {
+ return Promise.resolve(true);
+ }
+ return new Promise(resolve =>
+ setTimeout(function () {
+ waitUntil(predicate).then(() => resolve(true));
+ }, interval));
+}
diff --git a/devtools/server/tests/browser/browser_timeline_iframes.js b/devtools/server/tests/browser/browser_timeline_iframes.js
new file mode 100644
index 000000000..60728873f
--- /dev/null
+++ b/devtools/server/tests/browser/browser_timeline_iframes.js
@@ -0,0 +1,41 @@
+/* vim: set 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 the timeline front receives markers events for operations that occur in
+// iframes.
+
+const {TimelineFront} = require("devtools/shared/fronts/timeline");
+
+add_task(function* () {
+ let browser = yield addTab(MAIN_DOMAIN + "timeline-iframe-parent.html");
+ let doc = browser.contentDocument;
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let front = TimelineFront(client, form);
+
+ info("Start timeline marker recording");
+ yield front.start({ withMarkers: true });
+
+ // Check that we get markers for a few iterations of the timer that runs in
+ // the child frame.
+ for (let i = 0; i < 3; i++) {
+ yield wait(300); // That's the time the child frame waits before changing styles.
+ let markers = yield once(front, "markers");
+ ok(markers.length, "Markers were received for operations in the child frame");
+ }
+
+ info("Stop timeline marker recording");
+ yield front.stop();
+ yield client.close();
+ gBrowser.removeCurrentTab();
+});
+
+function wait(ms) {
+ return new Promise(resolve =>
+ setTimeout(resolve, ms));
+}
diff --git a/devtools/server/tests/browser/director-script-target.html b/devtools/server/tests/browser/director-script-target.html
new file mode 100644
index 000000000..0b0b56d64
--- /dev/null
+++ b/devtools/server/tests/browser/director-script-target.html
@@ -0,0 +1,15 @@
+<html>
+ <head>
+ <script>
+ // change the eval function to ensure the window object in the debug-script is correctly wrapped
+ window.eval = function () {
+ return "unsecure-eval-called";
+ };
+
+ var globalAccessibleVar = "global-value";
+ </script>
+ </head>
+ <body>
+ <h1>debug script target</h1>
+ </body>
+</html>
diff --git a/devtools/server/tests/browser/doc_allocations.html b/devtools/server/tests/browser/doc_allocations.html
new file mode 100644
index 000000000..314c40cac
--- /dev/null
+++ b/devtools/server/tests/browser/doc_allocations.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+</head>
+<body>
+<script>
+window.allocs = [];
+window.onload = function() {
+ function allocator() {
+ for (var i = 0; i < 1000; i++) {
+ window.allocs.push(new Object);
+ }
+ }
+
+ window.setInterval(allocator, 1);
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/browser/doc_force_cc.html b/devtools/server/tests/browser/doc_force_cc.html
new file mode 100644
index 000000000..d5868bd6b
--- /dev/null
+++ b/devtools/server/tests/browser/doc_force_cc.html
@@ -0,0 +1,29 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Performance tool + cycle collection test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ window.test = function () {
+ document.body.expando1 = { cycle: document.body };
+ SpecialPowers.Cu.forceCC();
+
+ document.body.expando2 = { cycle: document.body };
+ SpecialPowers.Cu.forceCC();
+
+ document.body.expando3 = { cycle: document.body };
+ SpecialPowers.Cu.forceCC();
+
+ setTimeout(window.test, 100);
+ };
+ test();
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/server/tests/browser/doc_force_gc.html b/devtools/server/tests/browser/doc_force_gc.html
new file mode 100644
index 000000000..f8b617533
--- /dev/null
+++ b/devtools/server/tests/browser/doc_force_gc.html
@@ -0,0 +1,27 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Performance tool + garbage collection test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ var x = 1;
+ window.test = function () {
+ SpecialPowers.Cu.forceGC();
+ document.body.style.borderTop = x + "px solid red";
+ x = 1^x;
+ document.body.innerHeight; // flush pending reflows
+
+ // Prevent this script from being garbage collected.
+ setTimeout(window.test, 100);
+ };
+ test();
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/server/tests/browser/doc_innerHTML.html b/devtools/server/tests/browser/doc_innerHTML.html
new file mode 100644
index 000000000..f5ce72de2
--- /dev/null
+++ b/devtools/server/tests/browser/doc_innerHTML.html
@@ -0,0 +1,21 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Performance tool + innerHTML test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ "use strict";
+ window.test = function () {
+ document.body.innerHTML = "<h1>LOL</h1>";
+ };
+ setInterval(window.test, 100);
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/server/tests/browser/doc_perf.html b/devtools/server/tests/browser/doc_perf.html
new file mode 100644
index 000000000..1da36328b
--- /dev/null
+++ b/devtools/server/tests/browser/doc_perf.html
@@ -0,0 +1,25 @@
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <title>Performance test page</title>
+ </head>
+
+ <body>
+ <script type="text/javascript">
+ var x = 1;
+ function test() {
+ document.body.style.borderTop = x + "px solid red";
+ x = 1^x;
+ document.body.innerHeight; // flush pending reflows
+ }
+
+ // Prevent this script from being garbage collected.
+ window.setInterval(test, 1);
+ </script>
+ </body>
+
+</html>
diff --git a/devtools/server/tests/browser/head.js b/devtools/server/tests/browser/head.js
new file mode 100644
index 000000000..1e7f09d95
--- /dev/null
+++ b/devtools/server/tests/browser/head.js
@@ -0,0 +1,203 @@
+/* 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/. */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {DebuggerClient} = require("devtools/shared/client/main");
+const {DebuggerServer} = require("devtools/server/main");
+const {defer} = require("promise");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const Services = require("Services");
+
+const PATH = "browser/devtools/server/tests/browser/";
+const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
+const ALT_DOMAIN = "http://sectest1.example.org/" + PATH;
+const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH;
+
+// All tests are asynchronous.
+waitForExplicitFinish();
+
+/**
+ * Add a new test tab in the browser and load the given url.
+ * @param {String} url The url to be loaded in the new tab
+ * @return a promise that resolves to the new browser that the document
+ * is loaded in. Note that we cannot return the document
+ * directly, since this would be a CPOW in the e10s case,
+ * and Promises cannot be resolved with CPOWs (see bug 1233497).
+ */
+var addTab = Task.async(function* (url) {
+ info(`Adding a new tab with URL: ${url}`);
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+ yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ info(`Tab added and URL ${url} loaded`);
+
+ return tab.linkedBrowser;
+});
+
+function* initAnimationsFrontForUrl(url) {
+ const {AnimationsFront} = require("devtools/shared/fronts/animation");
+ const {InspectorFront} = require("devtools/shared/fronts/inspector");
+
+ yield addTab(url);
+
+ initDebuggerServer();
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ let form = yield connectDebuggerClient(client);
+ let inspector = InspectorFront(client, form);
+ let walker = yield inspector.getWalker();
+ let animations = AnimationsFront(client, form);
+
+ return {inspector, walker, animations, client};
+}
+
+function initDebuggerServer() {
+ try {
+ // Sometimes debugger server does not get destroyed correctly by previous
+ // tests.
+ DebuggerServer.destroy();
+ } catch (e) {
+ info(`DebuggerServer destroy error: ${e}\n${e.stack}`);
+ }
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+}
+
+/**
+ * Connect a debugger client.
+ * @param {DebuggerClient}
+ * @return {Promise} Resolves to the selected tabActor form when the client is
+ * connected.
+ */
+function connectDebuggerClient(client) {
+ return client.connect()
+ .then(() => client.listTabs())
+ .then(tabs => {
+ return tabs.tabs[tabs.selected];
+ });
+}
+
+/**
+ * Wait for eventName on target.
+ * @param {Object} target An observable object that either supports on/off or
+ * addEventListener/removeEventListener
+ * @param {String} eventName
+ * @param {Boolean} useCapture Optional, for addEventListener/removeEventListener
+ * @return A promise that resolves when the event has been handled
+ */
+function once(target, eventName, useCapture = false) {
+ info("Waiting for event: '" + eventName + "' on " + target + ".");
+
+ return new Promise(resolve => {
+
+ for (let [add, remove] of [
+ ["addEventListener", "removeEventListener"],
+ ["addListener", "removeListener"],
+ ["on", "off"]
+ ]) {
+ if ((add in target) && (remove in target)) {
+ target[add](eventName, function onEvent(...aArgs) {
+ info("Got event: '" + eventName + "' on " + target + ".");
+ target[remove](eventName, onEvent, useCapture);
+ resolve(...aArgs);
+ }, useCapture);
+ break;
+ }
+ }
+ });
+}
+
+/**
+ * Forces GC, CC and Shrinking GC to get rid of disconnected docshells and
+ * windows.
+ */
+function forceCollections() {
+ Cu.forceGC();
+ Cu.forceCC();
+ Cu.forceShrinkingGC();
+}
+
+/**
+ * Get a mock tabActor from a given window.
+ * This is sometimes useful to test actors or classes that use the tabActor in
+ * isolation.
+ * @param {DOMWindow} win
+ * @return {Object}
+ */
+function getMockTabActor(win) {
+ return {
+ window: win,
+ isRootActor: true
+ };
+}
+
+registerCleanupFunction(function tearDown() {
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function idleWait(time) {
+ return DevToolsUtils.waitForTime(time);
+}
+
+function busyWait(time) {
+ let start = Date.now();
+ let stack;
+ while (Date.now() - start < time) { stack = Components.stack; }
+}
+
+/**
+ * Waits until a predicate returns true.
+ *
+ * @param function predicate
+ * Invoked once in a while until it returns true.
+ * @param number interval [optional]
+ * How often the predicate is invoked, in milliseconds.
+ */
+function waitUntil(predicate, interval = 10) {
+ if (predicate()) {
+ return Promise.resolve(true);
+ }
+ return new Promise(resolve => {
+ setTimeout(function () {
+ waitUntil(predicate).then(() => resolve(true));
+ }, interval);
+ });
+}
+
+function waitForMarkerType(front, types, predicate,
+ unpackFun = (name, data) => data.markers,
+ eventName = "timeline-data")
+{
+ types = [].concat(types);
+ predicate = predicate || function () { return true; };
+ let filteredMarkers = [];
+ let { promise, resolve } = defer();
+
+ info("Waiting for markers of type: " + types);
+
+ function handler(name, data) {
+ if (typeof name === "string" && name !== "markers") {
+ return;
+ }
+
+ let markers = unpackFun(name, data);
+ info("Got markers: " + JSON.stringify(markers, null, 2));
+
+ filteredMarkers = filteredMarkers.concat(markers.filter(m => types.indexOf(m.name) !== -1));
+
+ if (types.every(t => filteredMarkers.some(m => m.name === t)) && predicate(filteredMarkers)) {
+ front.off(eventName, handler);
+ resolve(filteredMarkers);
+ }
+ }
+ front.on(eventName, handler);
+
+ return promise;
+}
diff --git a/devtools/server/tests/browser/navigate-first.html b/devtools/server/tests/browser/navigate-first.html
new file mode 100644
index 000000000..829372427
--- /dev/null
+++ b/devtools/server/tests/browser/navigate-first.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+</head>
+<body>
+First
+<script>
+
+window.onbeforeunload=function(e){
+ e.returnValue="?";
+};
+</script>
+</body>
+</html>
diff --git a/devtools/server/tests/browser/navigate-second.html b/devtools/server/tests/browser/navigate-second.html
new file mode 100644
index 000000000..4b30fe465
--- /dev/null
+++ b/devtools/server/tests/browser/navigate-second.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+</head>
+<body>
+Second
+</body>
+</html>
diff --git a/devtools/server/tests/browser/storage-dynamic-windows.html b/devtools/server/tests/browser/storage-dynamic-windows.html
new file mode 100644
index 000000000..67aa35d67
--- /dev/null
+++ b/devtools/server/tests/browser/storage-dynamic-windows.html
@@ -0,0 +1,117 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 965872 - Storage inspector actor with cookies, local storage and session storage.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Storage inspector test for listing hosts and storages</title>
+</head>
+<body>
+<iframe src="http://sectest1.example.org/browser/devtools/server/tests/browser/storage-unsecured-iframe.html"></iframe>
+<script type="application/javascript;version=1.7">
+"use strict";
+const partialHostname = location.hostname.match(/^[^.]+(\..*)$/)[1];
+const cookieExpiresTime1 = 2000000000000;
+const cookieExpiresTime2 = 2000000001000;
+// Setting up some cookies to eat.
+document.cookie = "c1=foobar; expires=" +
+ new Date(cookieExpiresTime1).toGMTString() + "; path=/browser";
+document.cookie = "cs2=sessionCookie; path=/; domain=" + partialHostname;
+document.cookie = "c3=foobar-2; expires=" +
+ new Date(cookieExpiresTime2).toGMTString() + "; path=/";
+// ... and some local storage items ..
+localStorage.setItem("ls1", "foobar");
+localStorage.setItem("ls2", "foobar-2");
+// ... and finally some session storage items too
+sessionStorage.setItem("ss1", "foobar-3");
+
+let idbGenerator = function*() {
+ let request = indexedDB.open("idb1", 1);
+ request.onerror = function() {
+ throw new Error("error opening db connection");
+ };
+ let db = yield new Promise(done => {
+ request.onupgradeneeded = event => {
+ let db = event.target.result;
+ let store1 = db.createObjectStore("obj1", { keyPath: "id" });
+ store1.createIndex("name", "name", { unique: false });
+ store1.createIndex("email", "email", { unique: true });
+ let store2 = db.createObjectStore("obj2", { keyPath: "id2" });
+ store1.transaction.oncomplete = () => {
+ done(db);
+ };
+ };
+ });
+
+ // Prevents AbortError
+ yield new Promise(done => {
+ request.onsuccess = done;
+ });
+
+ let transaction = db.transaction(["obj1", "obj2"], "readwrite");
+ let store1 = transaction.objectStore("obj1");
+ let store2 = transaction.objectStore("obj2");
+ store1.add({id: 1, name: "foo", email: "foo@bar.com"});
+ store1.add({id: 2, name: "foo2", email: "foo2@bar.com"});
+ store1.add({id: 3, name: "foo2", email: "foo3@bar.com"});
+ store2.add({
+ id2: 1,
+ name: "foo",
+ email: "foo@bar.com",
+ extra: "baz"
+ });
+ // Prevents AbortError during close()
+ yield new Promise(success => {
+ transaction.oncomplete = success;
+ });
+
+ db.close();
+
+ request = indexedDB.open("idb2", 1);
+ let db2 = yield new Promise(done => {
+ request.onupgradeneeded = event => {
+ let db2 = event.target.result;
+ let store3 = db2.createObjectStore("obj3", { keyPath: "id3" });
+ store3.createIndex("name2", "name2", { unique: true });
+ store3.transaction.oncomplete = () => {
+ done(db2);
+ }
+ };
+ });
+ // Prevents AbortError during close()
+ yield new Promise(done => {
+ request.onsuccess = done;
+ });
+ db2.close();
+
+ console.log("added cookies and stuff from main page");
+};
+
+function deleteDB(dbName) {
+ return new Promise(resolve => {
+ dump("removing database " + dbName + " from " + document.location + "\n");
+ indexedDB.deleteDatabase(dbName).onsuccess = resolve;
+ });
+}
+
+window.setup = function*() {
+ yield idbGenerator();
+};
+
+window.clear = function*() {
+ document.cookie = "c1=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
+ document.cookie = "c3=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
+ document.cookie = "cs2=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
+
+ localStorage.clear();
+
+ yield deleteDB("idb1");
+ yield deleteDB("idb2");
+
+ dump("removed cookies, localStorage and indexedDB data from " +
+ document.location + "\n");
+};
+</script>
+</body>
+</html>
diff --git a/devtools/server/tests/browser/storage-helpers.js b/devtools/server/tests/browser/storage-helpers.js
new file mode 100644
index 000000000..1c4f37705
--- /dev/null
+++ b/devtools/server/tests/browser/storage-helpers.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This generator function opens the given url in a new tab, then sets up the
+ * page by waiting for all cookies, indexedDB items etc. to be created.
+ *
+ * @param url {String} The url to be opened in the new tab
+ *
+ * @return {Promise} A promise that resolves after storage inspector is ready
+ */
+function* openTabAndSetupStorage(url) {
+ let content = yield addTab(url);
+
+ // Setup the async storages in main window and for all its iframes
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ /**
+ * Get all windows including frames recursively.
+ *
+ * @param {Window} [baseWindow]
+ * The base window at which to start looking for child windows
+ * (optional).
+ * @return {Set}
+ * A set of windows.
+ */
+ function getAllWindows(baseWindow) {
+ let windows = new Set();
+
+ let _getAllWindows = function (win) {
+ windows.add(win.wrappedJSObject);
+
+ for (let i = 0; i < win.length; i++) {
+ _getAllWindows(win[i]);
+ }
+ };
+ _getAllWindows(baseWindow);
+
+ return windows;
+ }
+
+ let windows = getAllWindows(content);
+ for (let win of windows) {
+ if (win.setup) {
+ yield win.setup();
+ }
+ }
+ });
+}
+
+function* clearStorage() {
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function* () {
+ /**
+ * Get all windows including frames recursively.
+ *
+ * @param {Window} [baseWindow]
+ * The base window at which to start looking for child windows
+ * (optional).
+ * @return {Set}
+ * A set of windows.
+ */
+ function getAllWindows(baseWindow) {
+ let windows = new Set();
+
+ let _getAllWindows = function (win) {
+ windows.add(win.wrappedJSObject);
+
+ for (let i = 0; i < win.length; i++) {
+ _getAllWindows(win[i]);
+ }
+ };
+ _getAllWindows(baseWindow);
+
+ return windows;
+ }
+
+ let windows = getAllWindows(content);
+ for (let win of windows) {
+ if (win.clear) {
+ yield win.clear();
+ }
+ }
+ });
+}
diff --git a/devtools/server/tests/browser/storage-listings.html b/devtools/server/tests/browser/storage-listings.html
new file mode 100644
index 000000000..c3b7ef3c8
--- /dev/null
+++ b/devtools/server/tests/browser/storage-listings.html
@@ -0,0 +1,123 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 965872 - Storage inspector actor with cookies, local storage and session storage.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Storage inspector test for listing hosts and storages</title>
+</head>
+<body>
+<iframe src="http://sectest1.example.org/browser/devtools/server/tests/browser/storage-unsecured-iframe.html"></iframe>
+<iframe src="https://sectest1.example.org:443/browser/devtools/server/tests/browser/storage-secured-iframe.html"></iframe>
+<script type="application/javascript;version=1.7">
+"use strict";
+const partialHostname = location.hostname.match(/^[^.]+(\..*)$/)[1];
+const cookieExpiresTime1 = 2000000000000;
+const cookieExpiresTime2 = 2000000001000;
+// Setting up some cookies to eat.
+document.cookie = "c1=foobar; expires=" +
+ new Date(cookieExpiresTime1).toGMTString() + "; path=/browser";
+document.cookie = "cs2=sessionCookie; path=/; domain=" + partialHostname;
+document.cookie = "c3=foobar-2; secure=true; expires=" +
+ new Date(cookieExpiresTime2).toGMTString() + "; path=/";
+// ... and some local storage items ..
+localStorage.setItem("ls1", "foobar");
+localStorage.setItem("ls2", "foobar-2");
+// ... and finally some session storage items too
+sessionStorage.setItem("ss1", "foobar-3");
+console.log("added cookies and stuff from main page");
+
+let idbGenerator = function*() {
+ let request = indexedDB.open("idb1", 1);
+ request.onerror = function() {
+ throw new Error("error opening db connection");
+ };
+ let db = yield new Promise(done => {
+ request.onupgradeneeded = event => {
+ let db = event.target.result;
+ let store1 = db.createObjectStore("obj1", { keyPath: "id" });
+ store1.createIndex("name", "name", { unique: false });
+ store1.createIndex("email", "email", { unique: true });
+ let store2 = db.createObjectStore("obj2", { keyPath: "id2" });
+ store1.transaction.oncomplete = () => {
+ done(db);
+ };
+ };
+ });
+
+ // Prevents AbortError
+ yield new Promise(done => {
+ request.onsuccess = done;
+ });
+
+ let transaction = db.transaction(["obj1", "obj2"], "readwrite");
+ let store1 = transaction.objectStore("obj1");
+ let store2 = transaction.objectStore("obj2");
+ store1.add({id: 1, name: "foo", email: "foo@bar.com"});
+ store1.add({id: 2, name: "foo2", email: "foo2@bar.com"});
+ store1.add({id: 3, name: "foo2", email: "foo3@bar.com"});
+ store2.add({
+ id2: 1,
+ name: "foo",
+ email: "foo@bar.com",
+ extra: "baz"
+ });
+ // Prevents AbortError during close()
+ yield new Promise(success => {
+ transaction.oncomplete = success;
+ });
+
+ db.close();
+
+ request = indexedDB.open("idb2", 1);
+ let db2 = yield new Promise(done => {
+ request.onupgradeneeded = event => {
+ let db2 = event.target.result;
+ let store3 = db2.createObjectStore("obj3", { keyPath: "id3" });
+ store3.createIndex("name2", "name2", { unique: true });
+ store3.transaction.oncomplete = () => {
+ done(db2);
+ }
+ };
+ });
+ // Prevents AbortError during close()
+ yield new Promise(done => {
+ request.onsuccess = done;
+ });
+ db2.close();
+
+ dump("added cookies and stuff from main page\n");
+};
+
+function deleteDB(dbName) {
+ return new Promise(resolve => {
+ dump("removing database " + dbName + " from " + document.location + "\n");
+ indexedDB.deleteDatabase(dbName).onsuccess = resolve;
+ });
+}
+
+window.setup = function*() {
+ yield idbGenerator();
+};
+
+window.clear = function*() {
+ document.cookie = "c1=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/browser";
+ document.cookie =
+ "c3=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; secure=true";
+ document.cookie =
+ "cs2=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=" +
+ partialHostname;
+
+ localStorage.clear();
+ sessionStorage.clear();
+
+ yield deleteDB("idb1");
+ yield deleteDB("idb2");
+
+ dump("removed cookies, localStorage, sessionStorage and indexedDB data " +
+ "from " + document.location + "\n");
+};
+</script>
+</body>
+</html>
diff --git a/devtools/server/tests/browser/storage-secured-iframe.html b/devtools/server/tests/browser/storage-secured-iframe.html
new file mode 100644
index 000000000..860b20aab
--- /dev/null
+++ b/devtools/server/tests/browser/storage-secured-iframe.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Iframe for testing multiple host detetion in storage actor
+-->
+<head>
+ <meta charset="utf-8">
+</head>
+<body>
+<script type="application/javascript;version=1.7">
+
+document.cookie = "sc1=foobar;";
+localStorage.setItem("iframe-s-ls1", "foobar");
+sessionStorage.setItem("iframe-s-ss1", "foobar-2");
+
+let idbGenerator = function*() {
+ let request = indexedDB.open("idb-s1", 1);
+ request.onerror = function() {
+ throw new Error("error opening db connection");
+ };
+ let db = yield new Promise(done => {
+ request.onupgradeneeded = event => {
+ let db = event.target.result;
+ let store1 = db.createObjectStore("obj-s1", { keyPath: "id" });
+ store1.transaction.oncomplete = () => {
+ done(db);
+ };
+ };
+ });
+ yield new Promise(done => {
+ request.onsuccess = done;
+ });
+
+ let transaction = db.transaction(["obj-s1"], "readwrite");
+ let store1 = transaction.objectStore("obj-s1");
+ store1.add({id: 6, name: "foo", email: "foo@bar.com"});
+ store1.add({id: 7, name: "foo2", email: "foo2@bar.com"});
+ yield new Promise(success => {
+ transaction.oncomplete = success;
+ });
+
+ db.close();
+
+ request = indexedDB.open("idb-s2", 1);
+ let db2 = yield new Promise(done => {
+ request.onupgradeneeded = event => {
+ let db2 = event.target.result;
+ let store3 =
+ db2.createObjectStore("obj-s2", { keyPath: "id3", autoIncrement: true });
+ store3.createIndex("name2", "name2", { unique: true });
+ store3.transaction.oncomplete = () => {
+ done(db2);
+ };
+ };
+ });
+ yield new Promise(done => {
+ request.onsuccess = done;
+ });
+
+ transaction = db2.transaction(["obj-s2"], "readwrite");
+ let store3 = transaction.objectStore("obj-s2");
+ store3.add({id3: 16, name2: "foo", email: "foo@bar.com"});
+ yield new Promise(success => {
+ transaction.oncomplete = success;
+ });
+
+ db2.close();
+ dump("added cookies and stuff from secured iframe\n");
+}
+
+function deleteDB(dbName) {
+ return new Promise(resolve => {
+ dump("removing database " + dbName + " from " + document.location + "\n");
+ indexedDB.deleteDatabase(dbName).onsuccess = resolve;
+ });
+}
+
+window.setup = function*() {
+ yield idbGenerator();
+};
+
+window.clear = function*() {
+ document.cookie = "sc1=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
+
+ localStorage.clear();
+
+ yield deleteDB("idb-s1");
+ yield deleteDB("idb-s2");
+
+ console.log("removed cookies and stuff from secured iframe");
+}
+</script>
+</body>
+</html>
diff --git a/devtools/server/tests/browser/storage-unsecured-iframe.html b/devtools/server/tests/browser/storage-unsecured-iframe.html
new file mode 100644
index 000000000..d339fb7f2
--- /dev/null
+++ b/devtools/server/tests/browser/storage-unsecured-iframe.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Iframe for testing multiple host detetion in storage actor
+-->
+<head>
+ <meta charset="utf-8">
+</head>
+<body>
+<script>
+
+document.cookie = "uc1=foobar; domain=.example.org; path=/; secure=true";
+localStorage.setItem("iframe-u-ls1", "foobar");
+sessionStorage.setItem("iframe-u-ss1", "foobar1");
+sessionStorage.setItem("iframe-u-ss2", "foobar2");
+console.log("added cookies and stuff from unsecured iframe");
+
+window.clear = function*() {
+ document.cookie = "uc1=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
+ localStorage.clear();
+ sessionStorage.clear();
+ console.log("removed cookies and stuff from unsecured iframe");
+}
+</script>
+</body>
+</html>
diff --git a/devtools/server/tests/browser/storage-updates.html b/devtools/server/tests/browser/storage-updates.html
new file mode 100644
index 000000000..6ffaf4316
--- /dev/null
+++ b/devtools/server/tests/browser/storage-updates.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 965872 - Storage inspector actor with cookies, local storage and session storage.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Storage inspector blank html for tests</title>
+</head>
+<body>
+<script type="application/javascript;version=1.7">
+"use strict";
+window.addCookie = function(name, value, path, domain, expires, secure) {
+ let cookieString = name + "=" + value + ";";
+ if (path) {
+ cookieString += "path=" + path + ";";
+ }
+ if (domain) {
+ cookieString += "domain=" + domain + ";";
+ }
+ if (expires) {
+ cookieString += "expires=" + expires + ";";
+ }
+ if (secure) {
+ cookieString += "secure=true;";
+ }
+ document.cookie = cookieString;
+};
+
+window.removeCookie = function(name) {
+ document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
+};
+
+window.clearLocalAndSessionStores = function() {
+ localStorage.clear();
+ sessionStorage.clear();
+};
+
+window.clearCookies = function() {
+ let cookies = document.cookie;
+ for (let cookie of cookies.split(";")) {
+ removeCookie(cookie.split("=")[0]);
+ }
+};
+</script>
+</body>
+</html>
diff --git a/devtools/server/tests/browser/stylesheets-nested-iframes.html b/devtools/server/tests/browser/stylesheets-nested-iframes.html
new file mode 100644
index 000000000..7ee775323
--- /dev/null
+++ b/devtools/server/tests/browser/stylesheets-nested-iframes.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>StyleSheetsActor iframe test</title>
+ <style>
+ p {
+ padding: 1em;
+ }
+ </style>
+</head>
+<body>
+ <p>A test page with nested iframes</p>
+ <iframe></iframe>
+ <script type="application/javascript;version=1.8">
+ let iframe = document.querySelector("iframe");
+ let i = parseInt(location.href.split("?")[1]) || 1;
+
+ // The frame can't have the same src URL as any of its ancestors.
+ // This will not infinitely recurse because a frame won't get a content
+ // document once it's nested deeply enough.
+ iframe.src = location.href.split("?")[0] + "?" + (++i);
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/browser/timeline-iframe-child.html b/devtools/server/tests/browser/timeline-iframe-child.html
new file mode 100644
index 000000000..5385c6485
--- /dev/null
+++ b/devtools/server/tests/browser/timeline-iframe-child.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Timeline iframe test - child frame</title>
+</head>
+<body>
+ <h1>Child frame</h1>
+ <script>
+ var h1 = document.querySelector("h1");
+ setInterval(function() {
+ h1.style.backgroundColor = "rgb(" + ((Math.random()*255)|0) + "," +
+ ((Math.random()*255)|0) + "," +
+ ((Math.random()*255)|0) +")";
+ h1.style.width = ((Math.random()*500)|0) + "px";
+ }, 300);
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/browser/timeline-iframe-parent.html b/devtools/server/tests/browser/timeline-iframe-parent.html
new file mode 100644
index 000000000..b94ba4259
--- /dev/null
+++ b/devtools/server/tests/browser/timeline-iframe-parent.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Timeline iframe test - parent frame</title>
+</head>
+<body>
+ <h1>Parent frame</h1>
+ <iframe src="timeline-iframe-child.html"></iframe>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/.eslintrc.js b/devtools/server/tests/mochitest/.eslintrc.js
new file mode 100644
index 000000000..c5b919ce3
--- /dev/null
+++ b/devtools/server/tests/mochitest/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools mochitest eslintrc config.
+ "extends": "../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/server/tests/mochitest/Debugger.Source.prototype.element-2.js b/devtools/server/tests/mochitest/Debugger.Source.prototype.element-2.js
new file mode 100644
index 000000000..eab746921
--- /dev/null
+++ b/devtools/server/tests/mochitest/Debugger.Source.prototype.element-2.js
@@ -0,0 +1 @@
+debugger;
diff --git a/devtools/server/tests/mochitest/Debugger.Source.prototype.element.html b/devtools/server/tests/mochitest/Debugger.Source.prototype.element.html
new file mode 100644
index 000000000..fcf4c6c85
--- /dev/null
+++ b/devtools/server/tests/mochitest/Debugger.Source.prototype.element.html
@@ -0,0 +1,17 @@
+<head>
+ <!-- Static (not dynamically inserted) inline script. -->
+ <script id='franz'>function franz() { debugger; }</script>
+
+ <!-- Static out-of-line script element. -->
+ <script id='heinrich' src='Debugger.Source.prototype.element.js'></script>
+</head>
+
+<!-- HTML requires some body element onfoo attributes to add handlers to the
+ *window*, not the element --- but Debugger.Source.prototype.element should
+ return the element. Here, that rule should apply to the body's 'onresize'
+ handler. (For the reason for the 'cancelable' check, see the code that
+ sends the event.) -->
+<body onresize='if (event.cancelable) debugger;'>
+ <!-- Ordinary content element with event handler. -->
+ <div id='heidi' onclick='heinrichFun();'>Heidi</div>
+</body>
diff --git a/devtools/server/tests/mochitest/Debugger.Source.prototype.element.js b/devtools/server/tests/mochitest/Debugger.Source.prototype.element.js
new file mode 100644
index 000000000..44115b373
--- /dev/null
+++ b/devtools/server/tests/mochitest/Debugger.Source.prototype.element.js
@@ -0,0 +1 @@
+function heinrichFun() { franz(); }
diff --git a/devtools/server/tests/mochitest/animation-data.html b/devtools/server/tests/mochitest/animation-data.html
new file mode 100644
index 000000000..01be59548
--- /dev/null
+++ b/devtools/server/tests/mochitest/animation-data.html
@@ -0,0 +1,120 @@
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Animation Test Data</title>
+ <style>
+ .ball {
+ width: 80px;
+ height: 80px;
+ border-radius: 50%;
+ background: #f06;
+
+ position: absolute;
+ }
+
+ .still {
+ top: 0;
+ left: 10px;
+ }
+
+ .animated {
+ top: 100px;
+ left: 10px;
+
+ animation: simple-animation 2s infinite alternate;
+ }
+
+ .multi {
+ top: 200px;
+ left: 10px;
+
+ animation: simple-animation 2s infinite alternate,
+ other-animation 5s infinite alternate;
+ }
+
+ .delayed {
+ top: 300px;
+ left: 10px;
+ background: rebeccapurple;
+
+ animation: simple-animation 3s 60s 10;
+ }
+
+ .multi-finite {
+ top: 400px;
+ left: 10px;
+ background: yellow;
+
+ animation: simple-animation 3s,
+ other-animation 4s;
+ }
+
+ .short {
+ top: 500px;
+ left: 10px;
+ background: red;
+
+ animation: simple-animation 2s;
+ }
+
+ .long {
+ top: 600px;
+ left: 10px;
+ background: blue;
+
+ animation: simple-animation 120s;
+ }
+
+ .negative-delay {
+ top: 700px;
+ left: 10px;
+ background: gray;
+
+ animation: simple-animation 15s -10s;
+ animation-fill-mode: forwards;
+ }
+
+ .no-compositor {
+ top: 0;
+ right: 10px;
+ background: gold;
+
+ animation: no-compositor 10s cubic-bezier(.57,-0.02,1,.31) forwards;
+ }
+
+ @keyframes simple-animation {
+ 100% {
+ transform: translateX(300px);
+ }
+ }
+
+ @keyframes other-animation {
+ 100% {
+ background: blue;
+ }
+ }
+
+ @keyframes no-compositor {
+ 100% {
+ margin-right: 600px;
+ }
+ }
+ </style>
+ <script type="text/javascript">
+ window.onload = function() {
+ window.opener.postMessage('ready', '*');
+ };
+ </script>
+</head>
+</body>
+ <div class="ball still"></div>
+ <div class="ball animated"></div>
+ <div class="ball multi"></div>
+ <div class="ball delayed"></div>
+ <div class="ball multi-finite"></div>
+ <div class="ball short"></div>
+ <div class="ball long"></div>
+ <div class="ball negative-delay"></div>
+ <div class="ball no-compositor"></div>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/chrome.ini b/devtools/server/tests/mochitest/chrome.ini
new file mode 100644
index 000000000..ae69d163e
--- /dev/null
+++ b/devtools/server/tests/mochitest/chrome.ini
@@ -0,0 +1,103 @@
+[DEFAULT]
+tags = devtools
+skip-if = os == 'android'
+support-files =
+ animation-data.html
+ Debugger.Source.prototype.element.js
+ Debugger.Source.prototype.element-2.js
+ Debugger.Source.prototype.element.html
+ director-helpers.js
+ hello-actor.js
+ inspector_css-properties.html
+ inspector_getImageData.html
+ inspector-delay-image-response.sjs
+ inspector-eyedropper.html
+ inspector-helpers.js
+ inspector-search-data.html
+ inspector-styles-data.css
+ inspector-styles-data.html
+ inspector-traversal-data.html
+ large-image.jpg
+ memory-helpers.js
+ nonchrome_unsafeDereference.html
+ small-image.gif
+ setup-in-child.js
+ setup-in-parent.js
+
+[test_animation_actor-lifetime.html]
+[test_connection-manager.html]
+[test_connectToChild.html]
+[test_css-logic.html]
+[test_css-logic-media-queries.html]
+[test_css-logic-specificity.html]
+[test_css-properties_01.html]
+[test_css-properties_02.html]
+[test_Debugger.Source.prototype.introductionScript.html]
+[test_Debugger.Source.prototype.introductionType.html]
+[test_Debugger.Source.prototype.element.html]
+[test_Debugger.Script.prototype.global.html]
+[test_device.html]
+[test_director.html]
+[test_director_connectToChild.html]
+[test_executeInGlobal-outerized_this.html]
+[test_framerate_01.html]
+[test_framerate_02.html]
+[test_framerate_03.html]
+[test_framerate_04.html]
+[test_framerate_05.html]
+[test_framerate_06.html]
+[test_getProcess.html]
+[test_inspector-anonymous.html]
+[test_inspector-changeattrs.html]
+[test_inspector-changevalue.html]
+[test_inspector-dead-nodes.html]
+[test_inspector-duplicate-node.html]
+[test_inspector_getImageData.html]
+[test_inspector_getImageDataFromURL.html]
+[test_inspector_getImageData-wait-for-load.html]
+[test_inspector_getNodeFromActor.html]
+[test_inspector-hide.html]
+[test_inspector-insert.html]
+[test_inspector-mutations-attr.html]
+[test_inspector-mutations-events.html]
+[test_inspector-mutations-childlist.html]
+[test_inspector-mutations-frameload.html]
+[test_inspector-mutations-value.html]
+[test_inspector-pick-color.html]
+[test_inspector-pseudoclass-lock.html]
+[test_inspector-release.html]
+[test_inspector-reload.html]
+[test_inspector-remove.html]
+[test_inspector-resize.html]
+[test_inspector-resolve-url.html]
+[test_inspector-retain.html]
+[test_inspector-search.html]
+[test_inspector-search-front.html]
+[test_inspector-scroll-into-view.html]
+[test_inspector-traversal.html]
+[test_makeGlobalObjectReference.html]
+[test_memory.html]
+[test_memory_allocations_01.html]
+[test_memory_allocations_02.html]
+[test_memory_allocations_03.html]
+[test_memory_allocations_04.html]
+[test_memory_allocations_05.html]
+[test_memory_allocations_06.html]
+[test_memory_allocations_07.html]
+[test_memory_attach_01.html]
+[test_memory_attach_02.html]
+[test_memory_census.html]
+[test_memory_gc_01.html]
+[test_memory_gc_events.html]
+[test_preference.html]
+[test_settings.html]
+[test_setupInParentChild.html]
+[test_styles-applied.html]
+[test_styles-computed.html]
+[test_styles-layout.html]
+[test_styles-matched.html]
+[test_styles-modify.html]
+[test_styles-svg.html]
+[test_unsafeDereference.html]
+[test_websocket-server.html]
+skip-if = false
diff --git a/devtools/server/tests/mochitest/director-helpers.js b/devtools/server/tests/mochitest/director-helpers.js
new file mode 100644
index 000000000..fe1f7d394
--- /dev/null
+++ b/devtools/server/tests/mochitest/director-helpers.js
@@ -0,0 +1,44 @@
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {DebuggerClient} = require("devtools/shared/client/main");
+const {DebuggerServer} = require("devtools/server/main");
+const Services = require("Services");
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+Services.prefs.setBoolPref("dom.mozBrowserFramesEnabled", true);
+
+SimpleTest.registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+ Services.prefs.clearUserPref("dom.mozBrowserFramesEnabled");
+});
+
+const { DirectorRegistry } = require("devtools/server/actors/director-registry");
+const { DirectorRegistryFront } = require("devtools/shared/fronts/director-registry");
+
+const { DirectorManagerFront } = require("devtools/shared/fronts/director-manager");
+
+const { Task } = require("devtools/shared/task");
+
+/** *********************************
+ * director helpers functions
+ **********************************/
+
+function* newConnectedDebuggerClient(opts) {
+ var transport = DebuggerServer.connectPipe();
+ var client = new DebuggerClient(transport);
+
+ yield client.connect();
+
+ var root = yield client.listTabs();
+
+ return {
+ client: client,
+ root: root,
+ transport: transport
+ };
+}
+
+function purgeInstalledDirectorScripts() {
+ DirectorRegistry.clear();
+}
diff --git a/devtools/server/tests/mochitest/hello-actor.js b/devtools/server/tests/mochitest/hello-actor.js
new file mode 100644
index 000000000..603192938
--- /dev/null
+++ b/devtools/server/tests/mochitest/hello-actor.js
@@ -0,0 +1,26 @@
+/* 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: {
+ count: {
+ request: {},
+ response: {count: protocol.RetVal("number")}
+ }
+ }
+});
+
+var HelloActor = protocol.ActorClassWithSpec(helloSpec, {
+ initialize: function () {
+ protocol.Actor.prototype.initialize.apply(this, arguments);
+ this.counter = 0;
+ },
+
+ count: function () {
+ return ++this.counter;
+ }
+});
diff --git a/devtools/server/tests/mochitest/inspector-delay-image-response.sjs b/devtools/server/tests/mochitest/inspector-delay-image-response.sjs
new file mode 100644
index 000000000..215d1e4d8
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-delay-image-response.sjs
@@ -0,0 +1,42 @@
+/**
+ * Adapted from https://dxr.mozilla.org/mozilla-central/rev/
+ * 4e883591bb5dff021c108d3e30198a99547eed1e/layout/reftests/backgrounds/
+ * delay-image-response.sjs
+ */
+"use strict";
+
+// A 1x1 PNG image.
+// Source: https://commons.wikimedia.org/wiki/File:1x1.png (Public Domain)
+const IMAGE = atob("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEUAA" +
+ "ACnej3aAAAAAXRSTlMAQObYZgAAAApJREFUCNdjYAAAAAIAAeIhvDMAAAAASUVORK5CYII=");
+
+// To avoid GC.
+let timer = null;
+
+function handleRequest(request, response) {
+ let query = {};
+ request.queryString.split("&").forEach(function(val) {
+ let [name, value] = val.split("=");
+ query[name] = unescape(value);
+ });
+
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "image/png", false);
+
+ // If there is no delay, we write the image and leave.
+ if (!("delay" in query)) {
+ response.write(IMAGE);
+ return;
+ }
+
+ // If there is a delay, we create a timer which, when it fires, will write
+ // image and leave.
+ response.processAsync();
+ const nsITimer = Components.interfaces.nsITimer;
+
+ timer = Components.classes["@mozilla.org/timer;1"].createInstance(nsITimer);
+ timer.initWithCallback(function() {
+ response.write(IMAGE);
+ response.finish();
+ }, query.delay, nsITimer.TYPE_ONE_SHOT);
+}
diff --git a/devtools/server/tests/mochitest/inspector-eyedropper.html b/devtools/server/tests/mochitest/inspector-eyedropper.html
new file mode 100644
index 000000000..ded9f392d
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-eyedropper.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Inspector Eyedropper tests</title>
+ <style>
+ html {
+ background: black;
+ }
+ </style>
+ <script type="text/javascript">
+ window.onload = function() {
+ window.opener.postMessage('ready', '*');
+ };
+ </script>
+</head>
+</body>
+</body>
+</html> \ No newline at end of file
diff --git a/devtools/server/tests/mochitest/inspector-helpers.js b/devtools/server/tests/mochitest/inspector-helpers.js
new file mode 100644
index 000000000..47c643868
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-helpers.js
@@ -0,0 +1,310 @@
+var Cu = Components.utils;
+
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const {DebuggerClient} = require("devtools/shared/client/main");
+const {DebuggerServer} = require("devtools/server/main");
+const { Task } = require("devtools/shared/task");
+
+const Services = require("Services");
+const promise = require("promise");
+const {_documentWalker} = require("devtools/server/actors/inspector");
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+SimpleTest.registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+});
+
+
+if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ SimpleTest.registerCleanupFunction(function () {
+ DebuggerServer.destroy();
+ });
+}
+
+var gAttachCleanups = [];
+
+SimpleTest.registerCleanupFunction(function () {
+ for (let cleanup of gAttachCleanups) {
+ cleanup();
+ }
+});
+
+/**
+ * Open a tab, load the url, wait for it to signal its readiness,
+ * find the tab with the debugger server, and call the callback.
+ *
+ * Returns a function which can be called to close the opened ta
+ * and disconnect its debugger client.
+ */
+function attachURL(url, callback) {
+ var win = window.open(url, "_blank");
+ var client = null;
+
+ let cleanup = () => {
+ if (client) {
+ client.close();
+ client = null;
+ }
+ if (win) {
+ win.close();
+ win = null;
+ }
+ };
+ gAttachCleanups.push(cleanup);
+
+ window.addEventListener("message", function loadListener(event) {
+ if (event.data === "ready") {
+ client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(([applicationType, traits]) => {
+ client.listTabs(response => {
+ for (let tab of response.tabs) {
+ if (tab.url === url) {
+ window.removeEventListener("message", loadListener, false);
+ client.attachTab(tab.actor, function (aResponse, aTabClient) {
+ try {
+ callback(null, client, tab, win.document);
+ } catch (ex) {
+ Cu.reportError(ex);
+ dump(ex);
+ }
+ });
+ break;
+ }
+ }
+ });
+ });
+ }
+ }, false);
+
+ return cleanup;
+}
+
+function promiseOnce(target, event) {
+ let deferred = promise.defer();
+ target.on(event, (...args) => {
+ if (args.length === 1) {
+ deferred.resolve(args[0]);
+ } else {
+ deferred.resolve(args);
+ }
+ });
+ return deferred.promise;
+}
+
+function sortOwnershipChildren(children) {
+ return children.sort((a, b) => a.name.localeCompare(b.name));
+}
+
+function serverOwnershipSubtree(walker, node) {
+ let actor = walker._refMap.get(node);
+ if (!actor) {
+ return undefined;
+ }
+
+ let children = [];
+ let docwalker = new _documentWalker(node, window);
+ let child = docwalker.firstChild();
+ while (child) {
+ let item = serverOwnershipSubtree(walker, child);
+ if (item) {
+ children.push(item);
+ }
+ child = docwalker.nextSibling();
+ }
+ return {
+ name: actor.actorID,
+ children: sortOwnershipChildren(children)
+ };
+}
+
+function serverOwnershipTree(walker) {
+ let serverWalker = DebuggerServer._searchAllConnectionsForActor(walker.actorID);
+
+ return {
+ root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc),
+ orphaned: [...serverWalker._orphaned].map(o => serverOwnershipSubtree(serverWalker, o.rawNode)),
+ retained: [...serverWalker._retainedOrphans].map(o => serverOwnershipSubtree(serverWalker, o.rawNode))
+ };
+}
+
+function clientOwnershipSubtree(node) {
+ return {
+ name: node.actorID,
+ children: sortOwnershipChildren(node.treeChildren().map(child => clientOwnershipSubtree(child)))
+ };
+}
+
+function clientOwnershipTree(walker) {
+ return {
+ root: clientOwnershipSubtree(walker.rootNode),
+ orphaned: [...walker._orphaned].map(o => clientOwnershipSubtree(o)),
+ retained: [...walker._retainedOrphans].map(o => clientOwnershipSubtree(o))
+ };
+}
+
+function ownershipTreeSize(tree) {
+ let size = 1;
+ for (let child of tree.children) {
+ size += ownershipTreeSize(child);
+ }
+ return size;
+}
+
+function assertOwnershipTrees(walker) {
+ let serverTree = serverOwnershipTree(walker);
+ let clientTree = clientOwnershipTree(walker);
+ is(JSON.stringify(clientTree, null, " "), JSON.stringify(serverTree, null, " "), "Server and client ownership trees should match.");
+
+ return ownershipTreeSize(clientTree.root);
+}
+
+// Verify that an actorID is inaccessible both from the client library and the server.
+function checkMissing(client, actorID) {
+ let deferred = promise.defer();
+ let front = client.getActor(actorID);
+ ok(!front, "Front shouldn't be accessible from the client for actorID: " + actorID);
+
+ deferred = promise.defer();
+ client.request({
+ to: actorID,
+ type: "request",
+ }, response => {
+ is(response.error, "noSuchActor", "node list actor should no longer be contactable.");
+ deferred.resolve(undefined);
+ });
+ return deferred.promise;
+}
+
+// Verify that an actorID is accessible both from the client library and the server.
+function checkAvailable(client, actorID) {
+ let deferred = promise.defer();
+ let front = client.getActor(actorID);
+ ok(front, "Front should be accessible from the client for actorID: " + actorID);
+
+ deferred = promise.defer();
+ client.request({
+ to: actorID,
+ type: "garbageAvailableTest",
+ }, response => {
+ is(response.error, "unrecognizedPacketType", "node list actor should be contactable.");
+ deferred.resolve(undefined);
+ });
+ return deferred.promise;
+}
+
+function promiseDone(promise) {
+ promise.then(null, err => {
+ ok(false, "Promise failed: " + err);
+ if (err.stack) {
+ dump(err.stack);
+ }
+ SimpleTest.finish();
+ });
+}
+
+// Mutation list testing
+
+function isSrcChange(change) {
+ return (change.type === "attributes" && change.attributeName === "src");
+}
+
+function assertAndStrip(mutations, message, test) {
+ let size = mutations.length;
+ mutations = mutations.filter(test);
+ ok((mutations.size != size), message);
+ return mutations;
+}
+
+function isSrcChange(change) {
+ return change.type === "attributes" && change.attributeName === "src";
+}
+
+function isUnload(change) {
+ return change.type === "documentUnload";
+}
+
+function isFrameLoad(change) {
+ return change.type === "frameLoad";
+}
+
+function isUnretained(change) {
+ return change.type === "unretained";
+}
+
+function isChildList(change) {
+ return change.type === "childList";
+}
+
+function isNewRoot(change) {
+ return change.type === "newRoot";
+}
+
+// Make sure an iframe's src attribute changed and then
+// strip that mutation out of the list.
+function assertSrcChange(mutations) {
+ return assertAndStrip(mutations, "Should have had an iframe source change.", isSrcChange);
+}
+
+// Make sure there's an unload in the mutation list and strip
+// that mutation out of the list
+function assertUnload(mutations) {
+ return assertAndStrip(mutations, "Should have had a document unload change.", isUnload);
+}
+
+// Make sure there's a frame load in the mutation list and strip
+// that mutation out of the list
+function assertFrameLoad(mutations) {
+ return assertAndStrip(mutations, "Should have had a frame load change.", isFrameLoad);
+}
+
+// Make sure there's a childList change in the mutation list and strip
+// that mutation out of the list
+function assertChildList(mutations) {
+ return assertAndStrip(mutations, "Should have had a frame load change.", isChildList);
+}
+
+// Load mutations aren't predictable, so keep accumulating mutations until
+// the one we're looking for shows up.
+function waitForMutation(walker, test, mutations = []) {
+ let deferred = promise.defer();
+ for (let change of mutations) {
+ if (test(change)) {
+ deferred.resolve(mutations);
+ }
+ }
+
+ walker.once("mutations", newMutations => {
+ waitForMutation(walker, test, mutations.concat(newMutations)).then(finalMutations => {
+ deferred.resolve(finalMutations);
+ });
+ });
+
+ return deferred.promise;
+}
+
+
+var _tests = [];
+function addTest(test) {
+ _tests.push(test);
+}
+
+function addAsyncTest(generator) {
+ _tests.push(() => Task.spawn(generator).then(null, ok.bind(null, false)));
+}
+
+function runNextTest() {
+ if (_tests.length == 0) {
+ SimpleTest.finish();
+ return;
+ }
+ var fn = _tests.shift();
+ try {
+ fn();
+ } catch (ex) {
+ info("Test function " + (fn.name ? "'" + fn.name + "' " : "") +
+ "threw an exception: " + ex);
+ }
+}
diff --git a/devtools/server/tests/mochitest/inspector-search-data.html b/devtools/server/tests/mochitest/inspector-search-data.html
new file mode 100644
index 000000000..d0d5f9c68
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-search-data.html
@@ -0,0 +1,52 @@
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Inspector Search Test Data</title>
+ <style>
+ #pseudo {
+ display: block;
+ margin: 0;
+ }
+ #pseudo:before {
+ content: "before element";
+ }
+ #pseudo:after {
+ content: "after element";
+ }
+ </style>
+ <script type="text/javascript">
+ window.onload = function() {
+ window.opener.postMessage('ready', '*');
+ };
+ </script>
+</head>
+</body>
+ <!-- A comment
+ spread across multiple lines -->
+
+ <img width="100" height="100" src="large-image.jpg" />
+
+ <h1 id="pseudo">Heading 1</h1>
+ <p>A p tag with the text 'h1' inside of it.
+ <strong>A strong h1 result</strong>
+ </p>
+
+ <div id="arrows" northwest="↖" northeast="↗" southeast="↘" southwest="↙">
+ Unicode arrows
+ </div>
+
+ <h2>Heading 2</h2>
+ <h2>Heading 2</h2>
+ <h2>Heading 2</h2>
+
+ <h3>Heading 3</h3>
+ <h3>Heading 3</h3>
+ <h3>Heading 3</h3>
+
+ <h4>Heading 4</h4>
+ <h4>Heading 4</h4>
+ <h4>Heading 4</h4>
+
+ <div class="💩" id="💩" 💩="💩"></div>
+</body>
+</html> \ No newline at end of file
diff --git a/devtools/server/tests/mochitest/inspector-styles-data.css b/devtools/server/tests/mochitest/inspector-styles-data.css
new file mode 100644
index 000000000..5c3652f52
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-styles-data.css
@@ -0,0 +1,3 @@
+.external-rule {
+ cursor: crosshair;
+}
diff --git a/devtools/server/tests/mochitest/inspector-styles-data.html b/devtools/server/tests/mochitest/inspector-styles-data.html
new file mode 100644
index 000000000..a2a126f0e
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-styles-data.html
@@ -0,0 +1,81 @@
+<html>
+<script>
+ window.onload = () => {
+ window.opener.postMessage('ready', '*')
+ }
+</script>
+<style>
+ .inheritable-rule {
+ font-size: 15px;
+ }
+ .uninheritable-rule {
+ background-color: #f06;
+ }
+ @media screen {
+ #mediaqueried {
+ background-color: #f06;
+ }
+ }
+ #svgcontent rect {
+ fill: rgb(1,2,3);
+ }
+
+ #layout-element,
+ #layout-auto-margin-element {
+ width: 50px;
+ height: 50px;
+ padding: 3px 5px 7px 5px;
+ border: 5px solid red;
+ margin: 10px 20px 30px 0;
+ box-sizing: border-box;
+ position: absolute;
+ z-index: 2;
+ }
+
+ #layout-auto-margin-element {
+ margin: 10px auto;
+ }
+</style>
+<link type="text/css" rel="stylesheet" href="inspector-styles-data.css"></link>
+<body>
+ <h1>Style Actor Tests</h1>
+ <!-- Inheritance checks -->
+ <div id="inheritable-rule-uninheritable-style" class="inheritable-rule" style="background-color: purple">
+ <div id="inheritable-rule-inheritable-style" class="inheritable-rule" style="color: blue">
+ <div id="uninheritable-rule-uninheritable-style" class="uninheritable-rule" style="background-color: green">
+ <div id="uninheritable-rule-inheritable-style" class="uninheritable-rule" style="color: red">
+ <div id="test-node">
+ Here is the test node.
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Computed checks -->
+ <div id="computed-parent" class="external-rule inheritable-rule uninheritable-rule" style="color: red;">
+ <div id="computed-test-node" class="external-rule">
+ Here is the test node.
+ </div>
+ </div>
+
+ <!-- Matched checks -->
+ <div id="matched-parent" class="external-rule inheritable-rule uninheritable-rule" style="color: red;">
+ <div id="matched-test-node" style="font-size: 10px" class="external-rule">
+ Here is the test node.
+ </div>
+ </div>
+
+ <div id="mediaqueried">
+ Screen mediaqueried.
+ </div>
+
+ <div id="svgcontent">
+ <svg><rect></rect></svg>
+ </div>
+
+ <div id="layout-element">I can has layout</div>
+ <div id="layout-auto-margin-element">I can has layout too</div>
+
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/inspector-traversal-data.html b/devtools/server/tests/mochitest/inspector-traversal-data.html
new file mode 100644
index 000000000..45b8c2ede
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-traversal-data.html
@@ -0,0 +1,90 @@
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Inspector Traversal Test Data</title>
+ <style type="text/css">
+ #pseudo::before {
+ content: "before";
+ }
+ #pseudo::after {
+ content: "after";
+ }
+ #pseudo-empty::before {
+ content: "before an empty element";
+ }
+ #shadow::before {
+ content: "Testing ::before on a shadow host";
+ }
+ </style>
+ <script type="text/javascript">
+ window.onload = function() {
+
+ // Set up a basic shadow DOM
+ var host = document.querySelector('#shadow');
+ if (host.createShadowRoot) {
+ var root = host.createShadowRoot();
+ root.innerHTML = '<h3>Shadow <em>DOM</em></h3><select multiple></select>';
+ }
+
+ // Put a copy of the body in an iframe to test frame traversal.
+ var body = document.querySelector("body");
+ var data = "data:text/html,<html>" + body.outerHTML + "<html>";
+ var iframe = document.createElement("iframe");
+ iframe.setAttribute("id", "childFrame");
+ iframe.onload = function() {
+ window.opener.postMessage('ready', '*')
+ };
+ iframe.src = data;
+ body.appendChild(iframe);
+ }
+ </script>
+</head>
+<body style="background-color:white">
+ <h1>Inspector Actor Tests</h1>
+ <span id="longstring">longlonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglonglong</span>
+ <span id="shortstring">short</span>
+ <span id="empty"></span>
+ <div id="longlist" data-test="exists">
+ <div id="a">a</div>
+ <div id="b">b</div>
+ <div id="c">c</div>
+ <div id="d">d</div>
+ <div id="e">e</div>
+ <div id="f">f</div>
+ <div id="g">g</div>
+ <div id="h">h</div>
+ <div id="i">i</div>
+ <div id="j">j</div>
+ <div id="k">k</div>
+ <div id="l">l</div>
+ <div id="m">m</div>
+ <div id="n">n</div>
+ <div id="o">o</div>
+ <div id="p">p</div>
+ <div id="q">q</div>
+ <div id="r">r</div>
+ <div id="s">s</div>
+ <div id="t">t</div>
+ <div id="u">u</div>
+ <div id="v">v</div>
+ <div id="w">w</div>
+ <div id="x">x</div>
+ <div id="y">y</div>
+ <div id="z">z</div>
+ </div>
+ <div id="longlist-sibling">
+ <div id="longlist-sibling-firstchild"></div>
+ </div>
+ <p id="edit-html"></p>
+
+ <select multiple><option>one</option><option>two</option></select>
+ <div id="pseudo"><span>middle</span></div>
+ <div id="pseudo-empty"></div>
+ <div id="shadow">light dom</div>
+ <object>
+ <div id="1"></div>
+ </object>
+ <div class="node-to-duplicate"></div>
+ <div id="scroll-into-view" style="margin-top: 1000px;">scroll</div>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/inspector_css-properties.html b/devtools/server/tests/mochitest/inspector_css-properties.html
new file mode 100644
index 000000000..2c160c928
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector_css-properties.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<body>
+ <script type="text/javascript">
+ window.onload = function() {
+ window.opener.postMessage('ready', '*');
+ };
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/inspector_getImageData.html b/devtools/server/tests/mochitest/inspector_getImageData.html
new file mode 100644
index 000000000..eebecea90
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector_getImageData.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<body>
+ <img class="custom">
+ <img class="big-horizontal" src="large-image.jpg" style="width:500px;">
+ <canvas class="big-vertical" style="width:500px;"></canvas>
+ <img class="small" src="small-image.gif">
+ <img class="data" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAAJklEQVRIie3NMREAAAgAoe9fWls4eAzMVM0xoVAoFAqFQqFQ+C9chp4NHvu+4Q4AAAAASUVORK5CYII=">
+ <script>
+ window.onload = () => {
+ var canvas = document.querySelector("canvas"), ctx = canvas.getContext("2d");
+ canvas.width = 1000;
+ canvas.height = 2000;
+ ctx.fillStyle = "red";
+ ctx.fillRect(0, 0, 1000, 2000);
+
+ window.opener.postMessage('ready', '*')
+ }
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/large-image.jpg b/devtools/server/tests/mochitest/large-image.jpg
new file mode 100644
index 000000000..bda383e59
--- /dev/null
+++ b/devtools/server/tests/mochitest/large-image.jpg
Binary files differ
diff --git a/devtools/server/tests/mochitest/memory-helpers.js b/devtools/server/tests/mochitest/memory-helpers.js
new file mode 100644
index 000000000..aea8c4732
--- /dev/null
+++ b/devtools/server/tests/mochitest/memory-helpers.js
@@ -0,0 +1,52 @@
+var Cu = Components.utils;
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { Task } = require("devtools/shared/task");
+var Services = require("Services");
+var { DebuggerClient } = require("devtools/shared/client/main");
+var { DebuggerServer } = require("devtools/server/main");
+
+var { MemoryFront } = require("devtools/shared/fronts/memory");
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+SimpleTest.registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+});
+
+function startServerAndGetSelectedTabMemory() {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+
+ return client.connect()
+ .then(() => client.listTabs())
+ .then(response => {
+ var form = response.tabs[response.selected];
+ var memory = MemoryFront(client, form, response);
+
+ return { memory, client };
+ });
+}
+
+function destroyServerAndFinish(client) {
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish();
+ });
+}
+
+function waitForTime(ms) {
+ return new Promise((resolve, reject) => {
+ setTimeout(resolve, ms);
+ });
+}
+
+function waitUntil(predicate) {
+ if (predicate()) {
+ return Promise.resolve(true);
+ }
+ return new Promise(resolve => setTimeout(() => waitUntil(predicate).then(() => resolve(true)), 10));
+}
diff --git a/devtools/server/tests/mochitest/nonchrome_unsafeDereference.html b/devtools/server/tests/mochitest/nonchrome_unsafeDereference.html
new file mode 100644
index 000000000..6c19d3104
--- /dev/null
+++ b/devtools/server/tests/mochitest/nonchrome_unsafeDereference.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<script>
+var xhr = new XMLHttpRequest;
+xhr.timeout = 1742;
+xhr.expando = 'Expando!';
+</script>
+</html>
diff --git a/devtools/server/tests/mochitest/setup-in-child.js b/devtools/server/tests/mochitest/setup-in-child.js
new file mode 100644
index 000000000..a575faa20
--- /dev/null
+++ b/devtools/server/tests/mochitest/setup-in-child.js
@@ -0,0 +1,20 @@
+const {Cc, Ci} = require("chrome");
+const cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"].
+ getService(Ci.nsIMessageListenerManager);
+const { DebuggerServer } = require("devtools/server/main");
+
+exports.setupChild = function (a, b, c) {
+ cpmm.sendAsyncMessage("test:setupChild", [a, b, c]);
+};
+
+exports.callParent = function () {
+ // Hack! Fetch DebuggerServerConnection objects directly within DebuggerServer guts.
+ for (let id in DebuggerServer._connections) {
+ let conn = DebuggerServer._connections[id];
+ conn.setupInParent({
+ module: "chrome://mochitests/content/chrome/devtools/server/tests/mochitest/setup-in-parent.js",
+ setupParent: "setupParent",
+ args: [{one: true}, 2, "three"]
+ });
+ }
+};
diff --git a/devtools/server/tests/mochitest/setup-in-parent.js b/devtools/server/tests/mochitest/setup-in-parent.js
new file mode 100644
index 000000000..9845ee647
--- /dev/null
+++ b/devtools/server/tests/mochitest/setup-in-parent.js
@@ -0,0 +1,10 @@
+var {Ci} = require("chrome");
+var Services = require("Services");
+
+exports.setupParent = function ({mm, prefix}) {
+ let args = [
+ !!mm.QueryInterface(Ci.nsIMessageSender),
+ prefix
+ ];
+ Services.obs.notifyObservers(null, "test:setupParent", JSON.stringify(args));
+};
diff --git a/devtools/server/tests/mochitest/small-image.gif b/devtools/server/tests/mochitest/small-image.gif
new file mode 100644
index 000000000..e702427a5
--- /dev/null
+++ b/devtools/server/tests/mochitest/small-image.gif
Binary files differ
diff --git a/devtools/server/tests/mochitest/test_Debugger.Script.prototype.global.html b/devtools/server/tests/mochitest/test_Debugger.Script.prototype.global.html
new file mode 100644
index 000000000..77357e608
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_Debugger.Script.prototype.global.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=958646
+
+Debugger.Script.prototype.global should return innerize globals, not WindowProxies.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Debugger.Script.prototype.global should return inner windows</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+ SimpleTest.waitForExplicitFinish();
+
+ var iframe = document.createElement("iframe");
+ iframe.src = "data:text/html,<script>function glorp() { }<\/script>";
+ iframe.onload = firstOnLoadHandler;
+ document.body.appendChild(iframe);
+
+ function firstOnLoadHandler() {
+ var dbg = new Debugger;
+ var iframeDO = dbg.addDebuggee(iframe.contentWindow);
+
+ // For sanity: check that the debuggee global is the inner window,
+ // and that the outer window gets a distinct D.O.
+ var iframeWindowProxyDO = iframeDO.makeDebuggeeValue(iframe.contentWindow);
+ ok(iframeDO !== iframeWindowProxyDO);
+
+ // The real test: Debugger.Script.prototype.global returns inner windows.
+ ok(iframeDO.getOwnPropertyDescriptor('glorp').value.script.global === iframeDO);
+
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_Debugger.Source.prototype.element.html b/devtools/server/tests/mochitest/test_Debugger.Source.prototype.element.html
new file mode 100644
index 000000000..3cbc22353
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_Debugger.Source.prototype.element.html
@@ -0,0 +1,182 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=941876
+
+Debugger.Source.prototype.element and .elementAttributeName should report the DOM
+element to which code is attached (if any), and how.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Debugger.Source.prototype.element should return owning element</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+ SimpleTest.waitForExplicitFinish();
+
+ var log = '';
+ var doc, dieter, ulrich, isolde, albrecht;
+ var dbg, iframeDO, DOFor;
+
+ // Create an iframe to debug.
+ // We can't use a data: URL here, because we want to test script elements
+ // that refer to the JavaScript via 'src' attributes, and data: documents
+ // can't refer to those. So we use a separate HTML document.
+ var iframe = document.createElement("iframe");
+ iframe.src = "Debugger.Source.prototype.element.html";
+ iframe.onload = onLoadHandler;
+ document.body.appendChild(iframe);
+
+ function onLoadHandler() {
+ log += 'l';
+
+ // Now that the iframe's window has been created, we can add
+ // it as a debuggee.
+ dbg = new Debugger;
+ dbg.onDebuggerStatement = franzDebuggerHandler;
+ iframeDO = dbg.addDebuggee(iframe.contentWindow);
+ DOFor = iframeDO.makeDebuggeeValue.bind(iframeDO);
+
+ // Send a click event to heidi.
+ doc = iframe.contentWindow.document;
+ doc.getElementById('heidi').dispatchEvent(new Event('click'));
+ }
+
+ function franzDebuggerHandler(frame) {
+ log += 'f';
+
+ // The top stack frame should be franz, belonging to the script element.
+ ok(frame.callee.displayName === 'franz', 'top frame is franz');
+ ok(frame.script.source.element === DOFor(doc.getElementById('franz')),
+ 'top frame source belongs to element franz');
+ ok(frame.script.source.elementAttributeName === undefined,
+ "top frame source doesn't belong to an attribute");
+
+ // The second stack frame should belong to heinrich.
+ ok(frame.older.script.source.element === DOFor(doc.getElementById('heinrich')),
+ "second frame source belongs to element heinrich");
+ ok(frame.older.script.source.elementAttributeName === undefined,
+ "second frame source doesn't belong to an attribute");
+
+ // The next stack frame should belong to heidi's onclick handler.
+ ok(frame.older.older.script.source.element === DOFor(doc.getElementById('heidi')),
+ 'third frame source belongs to element heidi');
+ ok(frame.older.older.script.source.elementAttributeName === 'onclick',
+ "third frame source belongs to 'onclick' attribute");
+
+ // Try a dynamically inserted inline script element.
+ ulrich = doc.createElement('script');
+ ulrich.text = 'debugger;'
+ dbg.onDebuggerStatement = ulrichDebuggerHandler;
+ doc.body.appendChild(ulrich);
+ }
+
+ function ulrichDebuggerHandler(frame) {
+ log += 'u';
+
+ // The top frame should be ulrich's text.
+ ok(frame.script.source.element === DOFor(ulrich),
+ "top frame belongs to ulrich");
+ ok(frame.script.source.elementAttributeName === undefined,
+ "top frame is not on an attribute of ulrich");
+
+ // Try a dynamically inserted out-of-line script element.
+ isolde = doc.createElement('script');
+ isolde.setAttribute('src', 'Debugger.Source.prototype.element-2.js');
+ isolde.setAttribute('id', 'idolde, my dear');
+ dbg.onDebuggerStatement = isoldeDebuggerHandler;
+ doc.body.appendChild(isolde);
+ }
+
+ function isoldeDebuggerHandler(frame) {
+ log += 'i';
+
+ // The top frame should belong to isolde.
+ ok(frame.script.source.element === DOFor(isolde),
+ "top frame belongs to isolde");
+ info("frame.script.source.element is: " + uneval(frame.script.source.element));
+ if (typeof frame.script.source.element.unsafeDereference() == 'object') {
+ info(" toString: " + frame.script.source.element.unsafeDereference());
+ info(" id: " + frame.script.source.element.unsafeDereference().id);
+ }
+
+ ok(frame.script.source.elementAttributeName === undefined,
+ "top frame source is not an attribute of isolde");
+ info("frame.script.source.elementAttributeName is: " +
+ uneval(frame.script.source.elementAttributeName));
+
+ // Try a dynamically created div element with a handler.
+ dieter = doc.createElement('div');
+ dieter.setAttribute('id', 'dieter');
+ dieter.setAttribute('ondrag', 'debugger;');
+ dbg.onDebuggerStatement = dieterDebuggerHandler;
+ dieter.dispatchEvent(new Event('drag'));
+ }
+
+ function dieterDebuggerHandler(frame) {
+ log += 'd';
+
+ // The top frame should belong to dieter's ondrag handler.
+ ok(frame.script.source.element === DOFor(dieter),
+ "second event's handler belongs to dieter");
+ ok(frame.script.source.elementAttributeName === 'ondrag',
+ "second event's handler is on dieter's 'ondrag' element");
+
+ // Try sending an 'onresize' event to the window.
+ //
+ // Note that we only want Debugger to see the events we send, not any
+ // genuine resize events accidentally generated by the test harness (see bug
+ // 1162067). So we mark our events as cancelable; that seems to be the only
+ // bit chrome can fiddle on an Event that content code will see and that
+ // won't affect propagation. Then, the content event only runs its
+ // 'debugger' statement when the event is cancelable. It's a kludge.
+ dbg.onDebuggerStatement = resizeDebuggerHandler;
+ iframe.contentWindow.dispatchEvent(new Event('resize', { cancelable: true }));
+ }
+
+ function resizeDebuggerHandler(frame) {
+ log += 'e';
+
+ // The top frame should belong to the body's 'onresize' handler, even
+ // though we sent the message to the window and it was handled.
+ ok(frame.script.source.element === DOFor(doc.body),
+ "onresize event handler belongs to body element");
+ ok(frame.script.source.elementAttributeName === 'onresize',
+ "onresize event handler is on body element's 'onresize' attribute");
+
+ // In SVG, the event and the attribute that holds that event's handler
+ // have different names. Debugger.Source.prototype.elementAttributeName
+ // should report (as one might infer) the attribute name, not the event
+ // name.
+ albrecht = doc.createElementNS('http://www.w3.org/2000/svg', 'svg');
+ albrecht.setAttribute('onload', 'debugger;');
+ dbg.onDebuggerStatement = SVGLoadHandler;
+ albrecht.dispatchEvent(new Event("SVGLoad"));
+ }
+
+ function SVGLoadHandler(frame) {
+ log += 's';
+
+ // The top frame's source should be on albrecht's 'onload' attribute.
+ ok(frame.script.source.element === DOFor(albrecht),
+ "SVGLoad event handler belongs to albrecht");
+ ok(frame.script.source.elementAttributeName === 'onload',
+ "SVGLoad event handler is on albrecht's 'onload' attribute");
+
+ ok(log === 'lfuides', "all tests actually ran");
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionScript.html b/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionScript.html
new file mode 100644
index 000000000..cbc2ae615
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionScript.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=969786
+
+Debugger.Source.prototype.introductionScript and .introductionOffset should
+behave when 'eval' is called with no scripted frames active at all.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Debugger.Source.prototype.introductionScript with no caller</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+ SimpleTest.waitForExplicitFinish();
+
+ var dbg, iframeDO, doc, script2DO;
+
+ // Create an iframe to debug.
+ var iframe = document.createElement("iframe");
+ iframe.src = "data:text/html,<div>Hi!</div>";
+ iframe.onload = onLoadHandler;
+ document.body.appendChild(iframe);
+
+ function onLoadHandler() {
+ // Now that the iframe's window has been created, we can add
+ // it as a debuggee.
+ dbg = new Debugger;
+ iframeDO = dbg.addDebuggee(iframe.contentWindow);
+
+ doc = iframe.contentWindow.document;
+ var script = doc.createElement('script');
+ script.text = "setTimeout(eval.bind(null, 'debugger;'), 0);";
+ dbg.onDebuggerStatement = timerHandler;
+ doc.body.appendChild(script);
+ }
+
+ function timerHandler(frame) {
+ // The top stack frame's source should have an undefined
+ // introduction script and introduction offset.
+ var source = frame.script.source;
+ ok(source.introductionScript === undefined,
+ "setTimeout eval introductionScript is undefined");
+ ok(source.introductionOffset === undefined,
+ "setTimeout eval introductionOffset is undefined");
+
+ // Check that the above isn't just some quirk of iframes, or the
+ // browser milieu destroying information: an eval script should indeed
+ // have proper introduction information.
+ var script2 = doc.createElement('script');
+ script2.text = "eval('debugger;');";
+ script2DO = iframeDO.makeDebuggeeValue(script2);
+ dbg.onDebuggerStatement = evalHandler;
+ doc.body.appendChild(script2);
+ }
+
+ function evalHandler(frame) {
+ // The top stack frame's source should be introduced by the script that
+ // called eval.
+ var source = frame.script.source;
+ var frame2 = frame.older;
+
+ ok(source.introductionType === 'eval',
+ "top frame's source was introduced by 'eval'");
+ ok(source.introductionScript === frame2.script,
+ "eval frame's introduction script is the older frame's script");
+ ok(source.introductionOffset === frame2.offset,
+ "eval frame's introduction offset is current offset in older frame");
+ ok(source.introductionScript.source.element === script2DO,
+ "eval frame's introducer belongs to script2 element");
+
+ // The frame that called eval, in turn, should have no introduction
+ // information. (In the future, we certainly could point at the call
+ // that inserted the script element into the document; if that happens,
+ // we can update this test.)
+ ok(frame2.script.source.introductionType === 'scriptElement',
+ "older frame has no introduction type");
+ ok(frame2.script.source.introductionScript === undefined,
+ "older frame has no introduction script");
+ ok(frame2.script.source.introductionOffset === undefined,
+ "older frame has no introduction offset");
+
+ SimpleTest.finish();
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionType.html b/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionType.html
new file mode 100644
index 000000000..c0066659c
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_Debugger.Source.prototype.introductionType.html
@@ -0,0 +1,181 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=935203
+
+Debugger.Source.prototype.introductionType should return 'eventHandler' for
+JavaScrip appearing in an inline event handler attribute.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Debugger.Source.prototype.introductionType should identify event handlers</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+var dbg;
+var iframeDO, doc;
+var Tootles, TootlesDO;
+
+window.onload = function () {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+addTest(function setup() {
+ // Create an iframe to debug.
+ var iframe = document.createElement("iframe");
+ iframe.src = "data:text/html," +
+ "<div id='Tootles' onclick='debugger;'>I'm a DIV!</div>" +
+ "<script id='Auddie'>function auddie() { debugger; }<\/script>";
+ iframe.onload = onLoadHandler;
+ document.body.appendChild(iframe);
+
+ function onLoadHandler() {
+ // Now that the iframe's window has been created, we can add
+ // it as a debuggee.
+ dbg = new Debugger;
+ iframeDO = dbg.addDebuggee(iframe.contentWindow);
+ doc = iframe.contentWindow.document;
+ Tootles = doc.getElementById('Tootles');
+ TootlesDO = iframeDO.makeDebuggeeValue(Tootles);
+
+ runNextTest();
+ }
+});
+
+
+// Check the introduction type of in-markup event handler code.
+// Send a click event to Tootles, whose handler has a 'debugger' statement,
+// and check that script's introduction type.
+addTest(function ClickOnTootles() {
+ dbg.onDebuggerStatement = TootlesClickDebugger;
+ Tootles.dispatchEvent(new Event('click'));
+
+ function TootlesClickDebugger(frame) {
+ // some sanity checks
+ ok(frame.script.source.element === TootlesDO,
+ "top frame source belongs to element 'Tootles'");
+ is(frame.script.source.elementAttributeName, 'onclick',
+ "top frame source belongs to 'onclick' attribute");
+
+ // And, the actual point of this test:
+ is(frame.script.source.introductionType, 'eventHandler',
+ "top frame source's introductionType is 'eventHandler'");
+
+ runNextTest();
+ }
+});
+
+
+// Check the introduction type of dynamically added event handler code.
+// Add a drag event handler to Tootles as a string, and then send
+// Tootles a drag event.
+addTest(function DragTootles() {
+ dbg.onDebuggerStatement = TootlesDragDebugger;
+ Tootles.setAttribute('ondrag', 'debugger;');
+ Tootles.dispatchEvent(new Event('drag'));
+
+ function TootlesDragDebugger(frame) {
+ // sanity checks
+ ok(frame.script.source.element === TootlesDO,
+ "top frame source belongs to element 'Tootles'");
+ is(frame.script.source.elementAttributeName, 'ondrag',
+ "top frame source belongs to 'ondrag' attribute");
+
+ // And, the actual point of this test:
+ is(frame.script.source.introductionType, 'eventHandler',
+ "top frame source's introductionType is 'eventHandler'");
+
+ runNextTest();
+ }
+});
+
+
+// Check the introduction type of an in-markup script element.
+addTest(function checkAuddie() {
+ var fnDO = iframeDO.getOwnPropertyDescriptor('auddie').value;
+ var AuddieDO = iframeDO.makeDebuggeeValue(doc.getElementById('Auddie'));
+
+ is(fnDO.class, 'Function',
+ "Script element 'Auddie' defined function 'auddie'.");
+ ok(fnDO.script.source.element === AuddieDO,
+ "Function auddie's script belongs to script element 'Auddie'");
+ is(fnDO.script.source.elementAttributeName, undefined,
+ "Function auddie's script doesn't belong to any attribute of 'Auddie'");
+ is(fnDO.script.source.introductionType, 'scriptElement',
+ "Function auddie's script's source was introduced by a script element");
+
+ runNextTest();
+});
+
+
+// Check the introduction type of a dynamically inserted script element.
+addTest(function InsertRover() {
+ dbg.onDebuggerStatement = RoverDebugger;
+ var rover = doc.createElement('script');
+ var roverDO = iframeDO.makeDebuggeeValue(rover);
+ rover.text = 'debugger;';
+ doc.body.appendChild(rover);
+
+ function RoverDebugger(frame) {
+ // sanity checks
+ ok(frame.script.source.element === roverDO,
+ "Rover script belongs to Rover");
+ ok(frame.script.source.elementAttributeName === undefined,
+ "Rover script doesn't belong to an attribute of Rover");
+
+ // Check the introduction type.
+ ok(frame.script.source.introductionType === 'scriptElement',
+ "Rover script's introduction type is 'scriptElement'");
+
+ runNextTest();
+ }
+});
+
+
+// Create a XUL document with a script element, and check its introduction type.
+addTest(function XULDocumentScript() {
+ var xulFrame = document.createElement('iframe');
+ xulFrame.src = "data:application/vnd.mozilla.xul+xml;charset=utf-8," +
+ "<?xml version=\"1.0\"?>" +
+ "<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'>" +
+ "<script id='xulie'>function xulScriptFunc() { debugger; }<\/script>" +
+ "</window>";
+ xulFrame.onload = xulLoaded;
+ info("Appending iframe containing XUL document");
+ document.body.appendChild(xulFrame);
+
+ function xulLoaded() {
+ info("Loaded XUL document");
+ var xulFrameDO = dbg.addDebuggee(xulFrame.contentWindow);
+ var xulDoc = xulFrame.contentWindow.document;
+ var xulieDO = xulFrameDO.makeDebuggeeValue(xulDoc.getElementById('xulie'));
+ var xulFnDO = xulFrameDO.getOwnPropertyDescriptor('xulScriptFunc').value;
+ is(typeof xulFnDO, 'object', "XUL script element defined 'xulScriptFunc'");
+ is(xulFnDO.class, 'Function',
+ "XUL global 'xulScriptFunc' is indeed a function");
+
+ // A XUL document's script elements' code gets shared amongst all
+ // instantiations of the document, so there's no specific DOM element
+ // we can attribute the code to.
+ is(xulFnDO.script.source.element, undefined,
+ "XUL script code should not be attributed to any individual element");
+
+ is(xulFnDO.script.source.introductionType, 'scriptElement',
+ "xulScriptFunc's introduction type is 'scriptElement'");
+ runNextTest();
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_animation_actor-lifetime.html b/devtools/server/tests/mochitest/test_animation_actor-lifetime.html
new file mode 100644
index 000000000..a5265d918
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_animation_actor-lifetime.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1247243
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1247243</title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const Ci = Components.interfaces;
+ const {AnimationsFront} = require("devtools/shared/fronts/animation");
+ const {InspectorFront} = require("devtools/shared/fronts/inspector");
+
+ SimpleTest.waitForExplicitFinish();
+
+ let gWalker = null;
+ let gClient = null;
+ let animationsFront = null;
+
+ addTest(function setup() {
+ info ("Setting up inspector and animation actors.");
+
+ let url = document.getElementById("animationContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let inspector = InspectorFront(client, tab);
+
+ animationsFront = new AnimationsFront(client, tab);
+
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+
+ });
+ });
+
+ addAsyncTest(function* testActorLifetime() {
+
+ info ("Testing animated node actor");
+ let animatedNodeActor = yield gWalker.querySelector(gWalker.rootNode,
+ ".animated");
+ yield animationsFront.getAnimationPlayersForNode(animatedNodeActor);
+
+ let animationsActor = DebuggerServer._searchAllConnectionsForActor(animationsFront.actorID);
+
+ is(animationsActor.actors.length, 1,
+ "AnimationActor have 1 AnimationPlayerActors");
+
+ info ("Testing AnimationPlayerActors release");
+ let stillNodeActor = yield gWalker.querySelector(gWalker.rootNode,
+ ".still");
+ yield animationsFront.getAnimationPlayersForNode(stillNodeActor);
+ is(animationsActor.actors.length, 0,
+ "AnimationActor does not have any AnimationPlayerActors anymore");
+
+ info ("Testing multi animated node actor");
+ let multiNodeActor = yield gWalker.querySelector(gWalker.rootNode,
+ ".multi");
+ yield animationsFront.getAnimationPlayersForNode(multiNodeActor);
+ is(animationsActor.actors.length, 2,
+ "AnimationActor has now 2 AnimationPlayerActors");
+
+ info ("Testing single animated node actor");
+ yield animationsFront.getAnimationPlayersForNode(animatedNodeActor);
+ is(animationsActor.actors.length, 1,
+ "AnimationActor has only one AnimationPlayerActors");
+
+ info ("Testing AnimationPlayerActors release again");
+ yield animationsFront.getAnimationPlayersForNode(stillNodeActor);
+ is(animationsActor.actors.length, 0,
+ "AnimationActor does not have any AnimationPlayerActors anymore");
+
+ runNextTest();
+ });
+
+
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1247243">Mozilla Bug 1247243</a>
+ <a id="animationContent" target="_blank" href="animation-data.html">Test Document</a>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_connectToChild.html b/devtools/server/tests/mochitest/test_connectToChild.html
new file mode 100644
index 000000000..3bda7b566
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_connectToChild.html
@@ -0,0 +1,134 @@
+<SDOCTYPv HTM.>
+<html>
+<!--
+Bug 966991 - Test DebuggerServer.connectToChild
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript;version=1.8">
+
+let Cu = Components.utils;
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+
+let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+let { DebuggerClient } = require("devtools/shared/client/main");
+let { DebuggerServer } = require("devtools/server/main");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ // Always log packets when running tests.
+ ["devtools.debugger.log", true],
+ ["dom.mozBrowserFramesEnabled", true]
+ ]
+ }, runTests);
+}
+
+function runTests() {
+ // Create a minimal iframe with a message manager
+ let iframe = document.createElement("iframe");
+ iframe.mozbrowser = true;
+ document.body.appendChild(iframe);
+
+ let mm = iframe.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
+
+ // Register a test actor in the child process so that we can know if and when
+ // this fake actor is disconnected.
+ mm.loadFrameScript("data:text/javascript,new " + function FrameScriptScope() {
+ const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const { DebuggerServer } = require("devtools/server/main");
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ }
+
+ function TestActor() {dump("instanciate test actor\n");}
+ TestActor.prototype = {
+ actorPrefix: "test",
+
+ disconnect: function () {
+ sendAsyncMessage("test-actor-disconnected", null);
+ },
+ hello: function () {
+ return {msg:"world"};
+ }
+ };
+ TestActor.prototype.requestTypes = {
+ "hello": TestActor.prototype.hello
+ };
+ DebuggerServer.addTabActor(TestActor, "testActor");
+ }, false);
+
+ // Instantiate a minimal server
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ }
+ if (!DebuggerServer.createRootActor) {
+ DebuggerServer.addBrowserActors();
+ }
+
+ function firstClient() {
+ // Fake a first connection to an iframe
+ let transport = DebuggerServer.connectPipe();
+ let conn = transport._serverConnection;
+ let client = new DebuggerClient(transport);
+ DebuggerServer.connectToChild(conn, iframe).then(actor => {
+ ok(actor.testActor, "Got the test actor");
+
+ // Ensure sending at least one request to our actor,
+ // otherwise it won't be instanciated, nor be disconnected...
+ client.request({
+ to: actor.testActor,
+ type: "hello",
+ }, function (response) {
+
+ // Then close the client. That should end up cleaning our test actor
+ client.close();
+
+ // Ensure that our test actor got cleaned up;
+ // its disconnect method should be called
+ mm.addMessageListener("test-actor-disconnected", function listener() {
+ mm.removeMessageListener("test-actor-disconnected", listener);
+ ok(true, "Actor is cleaned up");
+
+ secondClient(actor.testActor);
+ });
+ });
+ });
+ }
+
+ function secondClient(firstActor) {
+ // Then fake a second one, that should spawn a new set of tab actors
+ let transport = DebuggerServer.connectPipe();
+ let conn = transport._serverConnection;
+ let client = new DebuggerClient(transport);
+ DebuggerServer.connectToChild(conn, iframe).then(actor => {
+ ok(actor.testActor, "Got a test actor for the second connection");
+ isnot(actor.testActor, firstActor, "We get different actor instances between two connections");
+
+ client.close(cleanup);
+ });
+ }
+
+ function cleanup() {
+ DebuggerServer.destroy();
+ iframe.remove();
+ SimpleTest.finish()
+ }
+
+ firstClient();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_connection-manager.html b/devtools/server/tests/mochitest/test_connection-manager.html
new file mode 100644
index 000000000..bc802f933
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_connection-manager.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 898485 - [app manager] Implement an abstract connection manager
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ var Cu = Components.utils;
+
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var {DebuggerServer} = require("devtools/server/main");
+ var Services = require("Services");
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ var {ConnectionManager, Connection} = require("devtools/shared/client/connection-manager");
+
+ var orgCount = ConnectionManager.connections.length;
+
+ ConnectionManager.once("new", (event, c) => {
+ is(ConnectionManager.connections[orgCount], c, "new event fired, with correct connection");
+ });
+
+ var c1 = ConnectionManager.createConnection();
+ var c2 = ConnectionManager.createConnection();
+
+ is(ConnectionManager.connections[orgCount], c1, "Connection 1 registered");
+ is(ConnectionManager.connections[orgCount + 1], c2, "Connection 2 registered");
+
+ c1.once(Connection.Events.DESTROYED, function() {
+ is(ConnectionManager.connections.length, orgCount + 1, "Connection 1 destroyed");
+
+ var c = c2;
+
+ var eventsRef = "connecting connected disconnecting disconnected host-changed disconnected timeout destroyed";
+ var events = [];
+
+ var s = Connection.Status;
+
+ is(c.status, s.DISCONNECTED, "disconnected");
+
+ c.once(Connection.Events.CONNECTING, function(e) { events.push(e); is(c.status, s.CONNECTING, "connecting"); });
+ c.once(Connection.Events.CONNECTED, function(e) { events.push(e); is(c.status, s.CONNECTED, "connected"); c.disconnect()});
+ c.once(Connection.Events.DISCONNECTING, function(e) { events.push(e); is(c.status, s.DISCONNECTING, "disconnecting"); });
+ c.once(Connection.Events.DISCONNECTED, function(e) { events.push(e); is(c.status, s.DISCONNECTED, "disconnected"); testError()});
+ c.once(Connection.Events.DESTROYED, function(e) { events.push(e); is(c.status, s.DESTROYED, "destroyed"); finish()});
+
+ c.connect();
+
+ function testStore() {
+ c.store.on("set", function(e,path) {
+ if (path.join(".") == "device.width") {
+ is(c.store.object.device.width, window.screen.width, "Store is fed with valid data");
+ c.disconnect();
+ }
+ });
+ }
+
+ function testError() {
+ c.once(Connection.Events.DISCONNECTED, function(e) {
+ events.push(e);
+ testKeepConnecting();
+ });
+ c.once(Connection.Events.HOST_CHANGED, function(e) {
+ events.push(e);
+ c.connect();
+ });
+ c.port = 1;
+ c.host = "localhost";
+ }
+
+ function testKeepConnecting() {
+ // ensure that keepConnecting keep trying connecting
+ // until the connection attempts timeout
+ var originalTimeout = Services.prefs.getIntPref("devtools.debugger.remote-timeout");
+ Services.prefs.setIntPref("devtools.debugger.remote-timeout", 1000);
+ c.once("timeout", function (e) {
+ events.push(e);
+ Services.prefs.setIntPref("devtools.debugger.remote-timeout", originalTimeout);
+ ConnectionManager.destroyConnection(c);
+ });
+ c.keepConnecting = true;
+ var port = ConnectionManager.getFreeTCPPort();
+ ok(parseInt(port), "Free TCP port looks like a port number");
+ c.port = port;
+ c.host = "locahost";
+ c.connect();
+ }
+
+ function finish() {
+ is(events.join(" "), eventsRef, "Events received in the right order");
+ DebuggerServer.destroy();
+ SimpleTest.finish();
+ }
+
+ });
+
+ ConnectionManager.destroyConnection(c1);
+
+
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_css-logic-media-queries.html b/devtools/server/tests/mochitest/test_css-logic-media-queries.html
new file mode 100644
index 000000000..bc465df55
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_css-logic-media-queries.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that css-logic handles media-queries correctly
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test css-logic media-queries</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <style>
+ div {
+ width: 1000px;
+ height: 100px;
+ background-color: #f00;
+ }
+
+ @media screen and (min-width: 1px) {
+ div {
+ width: 200px;
+ }
+ }
+ </style>
+</head>
+<body>
+ <div></div>
+ <script type="application/javascript;version=1.8">
+
+ window.onload = function() {
+ var { classes: Cc, utils: Cu, interfaces: Ci } = Components;
+ const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
+ .getService(Ci.inIDOMUtils);
+
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var Services = require("Services");
+ const {CssLogic} = require("devtools/server/css-logic");
+
+ SimpleTest.waitForExplicitFinish();
+
+ let div = document.querySelector("div");
+ let cssLogic = new CssLogic(DOMUtils.isInheritedProperty);
+ cssLogic.highlight(div);
+ cssLogic.processMatchedSelectors();
+
+ let _strings = Services.strings
+ .createBundle("chrome://devtools-shared/locale/styleinspector.properties");
+
+ let inline = _strings.GetStringFromName("rule.sourceInline");
+
+ let source1 = inline + ":12";
+ let source2 = inline + ":19 @media screen and (min-width: 1px)";
+ is(cssLogic._matchedRules[0][0].source, source1,
+ "rule.source gives correct output for rule 1");
+ is(cssLogic._matchedRules[1][0].source, source2,
+ "rule.source gives correct output for rule 2");
+
+ SimpleTest.finish();
+ }
+
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_css-logic-specificity.html b/devtools/server/tests/mochitest/test_css-logic-specificity.html
new file mode 100644
index 000000000..45169c1fd
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_css-logic-specificity.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that css-logic calculates CSS specificity properly
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test css-logic specificity</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body style="background:blue;">
+ <script type="application/javascript;version=1.8">
+
+ window.onload = function() {
+ var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
+
+ const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const {CssLogic, CssSelector} = require("devtools/server/css-logic");
+ const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
+ .getService(Ci.inIDOMUtils);
+
+ const TEST_DATA = [
+ {text: "*", expected: 0},
+ {text: "LI", expected: 1},
+ {text: "UL LI", expected: 2},
+ {text: "UL OL + LI", expected: 3},
+ {text: "H1 + [REL=\"up\"]", expected: 257},
+ {text: "UL OL LI.red", expected: 259},
+ {text: "LI.red.level", expected: 513},
+ {text: ".red .level", expected: 512},
+ {text: "#x34y", expected: 65536},
+ {text: "#s12:not(FOO)", expected: 65537},
+ {text: "body#home div#warning p.message", expected: 131331},
+ {text: "* body#home div#warning p.message", expected: 131331},
+ {text: "#footer :not(nav) li", expected: 65538},
+ {text: "bar:nth-child(n)", expected: 257},
+ {text: "li::-moz-list-number", expected: 1},
+ {text: "a:hover", expected: 257}
+ ];
+
+ function createDocument() {
+ let text = TEST_DATA.map(i=>i.text).join(",");
+ text = '<style type="text/css">' + text + " {color:red;}</style>";
+ document.body.innerHTML = text;
+ }
+
+ function getExpectedSpecificity(selectorText) {
+ return TEST_DATA.filter(i => i.text === selectorText)[0].expected;
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ createDocument();
+ let cssLogic = new CssLogic(DOMUtils.isInheritedProperty);
+
+ cssLogic.highlight(document.body);
+ let cssSheet = cssLogic.sheets[0];
+ let cssRule = cssSheet.domSheet.cssRules[0];
+ let selectors = CssLogic.getSelectors(cssRule);
+
+ info("Iterating over the test selectors")
+ for (let i = 0; i < selectors.length; i++) {
+ let selectorText = selectors[i];
+ info("Testing selector " + selectorText);
+
+ let selector = new CssSelector(cssRule, selectorText, i);
+ let expected = getExpectedSpecificity(selectorText);
+ let specificity = DOMUtils.getSpecificity(selector.cssRule,
+ selector.selectorIndex)
+ is(specificity, expected,
+ 'Selector "' + selectorText + '" has a specificity of ' + expected);
+ }
+
+ info("Testing specificity of element.style");
+ let colorProp = cssLogic.getPropertyInfo("background");
+ is(colorProp.matchedSelectors[0].specificity, 0x01000000,
+ "Element styles have specificity of 0x01000000 (16777216).");
+
+ SimpleTest.finish();
+ }
+
+ </script>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_css-logic.html b/devtools/server/tests/mochitest/test_css-logic.html
new file mode 100644
index 000000000..6c21e72c8
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_css-logic.html
@@ -0,0 +1,167 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const {CssLogic} = require("devtools/server/css-logic");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+addTest(function findAllCssSelectors() {
+ var nodes = document.querySelectorAll('*');
+ for (var i = 0; i < nodes.length; i++) {
+ var selector = CssLogic.findCssSelector(nodes[i]);
+ var matches = document.querySelectorAll(selector);
+
+ is(matches.length, 1, 'There is a single match: ' + selector);
+ is(matches[0], nodes[i], 'The selector matches the correct node: ' + selector);
+ }
+
+ runNextTest();
+});
+
+addTest(function findCssSelectorNotContainedInDocument() {
+
+ var unattached = document.createElement("div");
+ unattached.id = "unattached";
+ try {
+ CssLogic.findCssSelector(unattached);
+ ok (false, "Unattached node did not throw")
+ } catch(e) {
+ ok(e, "Unattached node throws an exception");
+ }
+
+ var unattachedChild = document.createElement("div");
+ unattached.appendChild(unattachedChild);
+ try {
+ CssLogic.findCssSelector(unattachedChild);
+ ok (false, "Unattached child node did not throw")
+ } catch(e) {
+ ok(e, "Unattached child node throws an exception");
+ }
+
+ var unattachedBody = document.createElement("body");
+ try {
+ CssLogic.findCssSelector(unattachedBody);
+ ok (false, "Unattached body node did not throw")
+ } catch(e) {
+ ok(e, "Unattached body node throws an exception");
+ }
+
+ runNextTest();
+});
+
+addTest(function findCssSelector() {
+
+ let data = [
+ "#one",
+ "#" + CSS.escape("2"),
+ ".three",
+ "." + CSS.escape("4"),
+ "#find-css-selector > div:nth-child(5)",
+ "#find-css-selector > p:nth-child(6)",
+ ".seven",
+ ".eight",
+ ".nine",
+ ".ten",
+ "div.sameclass:nth-child(11)",
+ "div.sameclass:nth-child(12)",
+ "div.sameclass:nth-child(13)",
+ "#" + CSS.escape("!, \", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \\, ], ^, `, {, |, }, ~"),
+ ];
+
+ let container = document.querySelector("#find-css-selector");
+ is (container.children.length, data.length, "Container has correct number of children.");
+
+ for (let i = 0; i < data.length; i++) {
+ let node = container.children[i];
+ is (CssLogic.findCssSelector(node), data[i], "matched id for index " + (i-1));
+ }
+
+ runNextTest();
+});
+
+addTest(function getComputedStyle() {
+ let node = document.querySelector("#computed-style");
+ is (CssLogic.getComputedStyle(node).getPropertyValue("width"),
+ "50px", "Computed style on a normal node works (width)");
+ is (CssLogic.getComputedStyle(node).getPropertyValue("height"),
+ "10px", "Computed style on a normal node works (height)");
+
+ let firstChild = new _documentWalker(node, window).firstChild();
+ is (CssLogic.getComputedStyle(firstChild).getPropertyValue("content"),
+ "\"before\"", "Computed style on a ::before node works (content)");
+ let lastChild = new _documentWalker(node, window).lastChild();
+ is (CssLogic.getComputedStyle(lastChild).getPropertyValue("content"),
+ "\"after\"", "Computed style on a ::after node works (content)");
+
+ runNextTest();
+});
+
+addTest(function getBindingElementAndPseudo() {
+ let node = document.querySelector("#computed-style");
+ var {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(node);
+
+ is (bindingElement, node,
+ "Binding element is the node itself for a normal node");
+ ok (!pseudo, "Pseudo is null for a normal node");
+
+ let firstChild = new _documentWalker(node, window).firstChild();
+ var {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(firstChild);
+ is (bindingElement, node,
+ "Binding element is the parent for a pseudo node");
+ is (pseudo, ":before", "Pseudo is correct for a ::before node");
+
+ let lastChild = new _documentWalker(node, window).lastChild();
+ var {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(lastChild);
+ is (bindingElement, node,
+ "Binding element is the parent for a pseudo node");
+ is (pseudo, ":after", "Pseudo is correct for a ::after node");
+
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+ <div id="find-css-selector">
+ <div id="one"></div> <!-- Basic ID -->
+ <div id="2"></div> <!-- Escaped ID -->
+ <div class="three"></div> <!-- Basic Class -->
+ <div class="4"></div> <!-- Escaped Class -->
+ <div attr="5"></div> <!-- Only an attribute -->
+ <p></p> <!-- Nothing unique -->
+ <div class="seven seven"></div> <!-- Two classes with same name -->
+ <div class="eight eight2"></div> <!-- Two classes with different names -->
+
+ <!-- Two elements with the same id - should not use ID -->
+ <div class="nine" id="nine-and-ten"></div>
+ <div class="ten" id="nine-and-ten"></div>
+
+ <!-- Three elements with the same id - should use class and nth-child instead -->
+ <div class="sameclass" id="11-12-13"></div>
+ <div class="sameclass" id="11-12-13"></div>
+ <div class="sameclass" id="11-12-13"></div>
+
+ <!-- Special characters -->
+ <div id="!, &quot;, #, $, %, &amp;, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, `, {, |, }, ~"></div>
+ </div>
+ <style type="text/css">
+ #computed-style { width: 50px; height: 10px; }
+ #computed-style::before { content: "before"; }
+ #computed-style::after { content: "after"; }
+ </style>
+ <div id="computed-style"></div>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_css-properties_01.html b/devtools/server/tests/mochitest/test_css-properties_01.html
new file mode 100644
index 000000000..45386b830
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_css-properties_01.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1265798 - Replace inIDOMUtils.cssPropertyIsShorthand
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test CSS Properties Actor</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const { initCssProperties, getCssProperties } =
+ require("devtools/shared/fronts/css-properties");
+
+ function promiseAttachUrl (url) {
+ return new Promise((resolve, reject) => {
+ attachURL(url, function(err, client, tab, doc) {
+ if (err) {
+ return reject(err);
+ }
+ resolve({client, tab, doc});
+ });
+ })
+ }
+
+ function toSortedString(array) {
+ return JSON.stringify(array.sort());
+ }
+
+ const runCssPropertiesTests = Task.async(function* (url, useActor) {
+ info(`Opening two tabs ${useActor ? "with" : "without"} CssPropertiesActor support.`);
+
+ let attachmentA = yield promiseAttachUrl(url);
+ let attachmentB = yield promiseAttachUrl(url);
+
+ const toolboxMockA = {
+ target: {
+ hasActor: () => useActor,
+ client: attachmentA.client,
+ form: attachmentA.tab
+ },
+ // Fake the window for css-properties.js's getClientBrowserVersion to work
+ win: window
+ };
+ const toolboxMockB = {
+ target: {
+ hasActor: () => useActor,
+ client: attachmentB.client,
+ form: attachmentB.tab
+ },
+ win: window
+ };
+
+ yield initCssProperties(toolboxMockA);
+ yield initCssProperties(toolboxMockB);
+
+ const cssProperties = getCssProperties(toolboxMockA);
+ const cssPropertiesA = getCssProperties(toolboxMockA);
+ const cssPropertiesB = getCssProperties(toolboxMockB);
+
+ is(cssProperties, cssPropertiesA,
+ "Multiple calls with the same toolbox returns the same object.");
+ isnot(cssProperties, cssPropertiesB,
+ "Multiple calls with the different toolboxes return different "+
+ " objects.");
+
+ ok(cssProperties.isKnown("border"),
+ "The `border` shorthand property is known.");
+ ok(cssProperties.isKnown("display"),
+ "The `display` property is known.");
+ ok(!cssProperties.isKnown("foobar"),
+ "A fake property is not known.");
+ ok(cssProperties.isKnown("--foobar"),
+ "A CSS variable properly evaluates.");
+ ok(cssProperties.isKnown("--foob\\{ar"),
+ "A CSS variable with escaped character properly evaluates.");
+ ok(cssProperties.isKnown("--fübar"),
+ "A CSS variable unicode properly evaluates.");
+ ok(!cssProperties.isKnown("--foo bar"),
+ "A CSS variable with spaces fails");
+
+ is(toSortedString(cssProperties.getValues('margin')),
+ toSortedString(["-moz-calc","auto","calc","inherit","initial","unset"]),
+ "Can get values for the CSS margin.");
+ is(cssProperties.getValues('foobar').length, 0,
+ "Unknown values return an empty array.");
+
+ const bgColorValues = cssProperties.getValues('background-color');
+ ok(bgColorValues.includes("blanchedalmond"),
+ "A property with color values includes blanchedalmond.");
+ ok(bgColorValues.includes("papayawhip"),
+ "A property with color values includes papayawhip.");
+ ok(bgColorValues.includes("rgb"),
+ "A property with color values includes non-colors.");
+
+ ok(cssProperties.isValidOnClient("margin", "0px", window.document),
+ "Margin and 0px are valid CSS values");
+ ok(!cssProperties.isValidOnClient("margin", "foo", window.document),
+ "Margin and foo are not valid CSS values");
+ });
+
+ addAsyncTest(function* setup() {
+ let url = document.getElementById("cssProperties").href;
+ yield runCssPropertiesTests(url, true);
+ yield runCssPropertiesTests(url, false);
+
+ runNextTest();
+ });
+
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+ </script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265798">Mozilla Bug 1265798</a>
+ <a id="cssProperties" target="_blank" href="inspector_css-properties.html">Test Document</a>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_css-properties_02.html b/devtools/server/tests/mochitest/test_css-properties_02.html
new file mode 100644
index 000000000..1a5d99d72
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_css-properties_02.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1265798 - Replace inIDOMUtils.cssPropertyIsShorthand
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test CSS Properties Actor</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const { initCssProperties, getCssProperties } =
+ require("devtools/shared/fronts/css-properties");
+
+ const { CSS_PROPERTIES_DB } = require("devtools/shared/css/properties-db");
+
+ function promiseAttachUrl (url) {
+ return new Promise((resolve, reject) => {
+ attachURL(url, function(err, client, tab, doc) {
+ if (err) {
+ return reject(err);
+ }
+ resolve({client, tab, doc});
+ });
+ })
+ }
+
+ addAsyncTest(function* setup() {
+ let url = document.getElementById("cssProperties").href;
+
+ let attachmentA = yield promiseAttachUrl(url);
+ let attachmentB = yield promiseAttachUrl(url);
+ let attachmentC = yield promiseAttachUrl(url);
+
+ const toolboxMatchingVersions = {
+ target: {
+ hasActor: () => true,
+ client: attachmentA.client,
+ form: attachmentA.tab,
+ },
+ win: window
+ };
+ const toolboxDifferentVersions = {
+ target: {
+ hasActor: () => true,
+ client: attachmentB.client,
+ form: attachmentB.tab
+ },
+ win: { navigator: { userAgent:
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:49.0) Gecko/20100101 " +
+ "Firefox/30.0" }}
+ };
+
+ // Modify a property on the static database, to differentiate between a generated
+ // and static CSS properties database.
+ CSS_PROPERTIES_DB.properties.color.isStatic = true;
+
+ yield initCssProperties(toolboxMatchingVersions);
+ yield initCssProperties(toolboxDifferentVersions);
+
+ const cssPropertiesMatching = getCssProperties(toolboxMatchingVersions);
+ const cssPropertiesDifferent = getCssProperties(toolboxDifferentVersions);
+
+ is(cssPropertiesMatching.properties.color.isStatic, true,
+ "The static CSS database is used when the client and platform versions match.");
+ isnot(cssPropertiesDifferent.properties.color.isStatic, undefined,
+ "The generated CSS database is used when the client and platform versions do " +
+ "not match, but the client is a Firefox.");
+
+ delete CSS_PROPERTIES_DB.properties.color.isStatic;
+
+ runNextTest();
+ });
+
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+ </script>
+</head>
+<body>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1265798">Mozilla Bug 1265798</a>
+ <a id="cssProperties" target="_blank" href="inspector_css-properties.html">Test Document</a>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_device.html b/devtools/server/tests/mochitest/test_device.html
new file mode 100644
index 000000000..d678f185f
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_device.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 895360 - [app manager] Device meta data actor
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+window.onload = function() {
+ var Cu = Components.utils;
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ Cu.import("resource://gre/modules/PermissionsTable.jsm");
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var {DebuggerClient} = require("devtools/shared/client/main");
+ var {DebuggerServer} = require("devtools/server/main");
+ var Services = require("Services");
+
+ SimpleTest.waitForExplicitFinish();
+
+ var {getDeviceFront} = require("devtools/shared/fronts/device");
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(function onListTabs(aResponse) {
+ var d = getDeviceFront(client, aResponse);
+
+ var desc, permissions;
+ var appInfo = Services.appinfo;
+ var utils = window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+
+
+ var localDesc = {
+ appid: appInfo.ID,
+ vendor: appInfo.vendor,
+ name: appInfo.name,
+ version: appInfo.version,
+ appbuildid: appInfo.appBuildID,
+ platformbuildid: appInfo.platformBuildID,
+ platformversion: appInfo.platformVersion,
+ geckobuildid: appInfo.platformBuildID,
+ geckoversion: appInfo.platformVersion,
+ useragent: window.navigator.userAgent,
+ locale: Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global"),
+ os: appInfo.OS,
+ processor: appInfo.XPCOMABI.split("-")[0],
+ compiler: appInfo.XPCOMABI.split("-")[1],
+ dpi: utils.displayDPI,
+ width: window.screen.width,
+ height: window.screen.height
+ }
+
+ function checkValues() {
+ for (var key in localDesc) {
+ is(desc[key], localDesc[key], "valid field (" + key + ")");
+ }
+
+ var currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ var profileDir = currProfD.path;
+ ok(profileDir.indexOf(desc.profile.length > 0 && desc.profile) != -1, "valid profile name");
+
+ var a = JSON.stringify(PermissionsTable);
+ var b = JSON.stringify(permissions.rawPermissionsTable);
+
+ is(a, b, "Permissions Tables is valid");
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ }
+
+
+ d.getDescription().then((v) => desc = v)
+ .then(() => d.getRawPermissionsTable())
+ .then((json) => permissions = json)
+ .then(checkValues);
+
+ });
+ });
+
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_director.html b/devtools/server/tests/mochitest/test_director.html
new file mode 100644
index 000000000..ad2648bfa
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_director.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+ <script type="application/javascript;version=1.8" src="./director-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const WAIT_EVENT_TIMEOUT = 3000;
+
+window.onload = function() {
+ Task.spawn(function* () {
+ SimpleTest.waitForExplicitFinish();
+
+ var tests = [
+ runDirectorRegistryActorTest
+ ].map((testCase) => {
+ return function* () {
+ setup();
+ yield testCase().then(null, (e) => {
+ console.error("Exception during testCase run", e);
+ ok(false, "Exception during testCase run: " + [e, e.fileName, e.lineNumber].join("\n\t"));
+ });
+
+ teardown();
+ };
+ });
+
+ for (var test of tests) {
+ yield test();
+ }
+ }).then(
+ function success() {
+ SimpleTest.finish()
+ },
+ function error(e) {
+ console.error("Exception during testCase run", e);
+ ok(false, "Exception during testCase run: " + [e, e.fileName, e.lineNumber].join("\n\t"));
+
+ SimpleTest.finish();
+ }
+ );
+};
+
+var targetWin = null;
+
+function setup() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init(() => true);
+ DebuggerServer.addBrowserActors();
+
+ SimpleTest.registerCleanupFunction(teardown);
+ }
+}
+
+function teardown() {
+ purgeInstalledDirectorScripts();
+
+ DebuggerServer.destroy();
+ if (targetWin) {
+ targetWin.close();
+ }
+}
+
+/***********************************
+ * test cases
+ **********************************/
+
+
+function runDirectorRegistryActorTest() {
+ let testDirectorScriptOptions = {
+ scriptCode: "(" + (function() {
+ module.exports = function({port}) {
+ port.onmessage = function(evt) {
+ // echo messages
+ evt.source.postMessage(evt.data);
+ };
+ };
+ }).toString() + ")();",
+ scriptOptions: {}
+ }
+
+ return Task.spawn(function* () {
+ let { client, root } = yield newConnectedDebuggerClient();
+
+ var directorRegistryClient = new DirectorRegistryFront(client, root);
+
+ let installed = yield directorRegistryClient.install("testDirectorScript", testDirectorScriptOptions);
+ is(installed, true, "DirectorManager.install returns true");
+
+ let list = yield directorRegistryClient.list();
+ is(JSON.stringify(list), JSON.stringify(["testDirectorScript"]),
+ "DirectorManager.list contains the installed director script");
+
+ let uninstalled = yield directorRegistryClient.uninstall("testDirectorScript");
+ is(uninstalled, true, "DirectorManager.uninstall return true");
+
+ yield client.close();
+ });
+}
+
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_director_connectToChild.html b/devtools/server/tests/mochitest/test_director_connectToChild.html
new file mode 100644
index 000000000..cb348efe6
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_director_connectToChild.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+ <script type="application/javascript;version=1.8" src="./director-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ Task.spawn(function* () {
+ SimpleTest.waitForExplicitFinish();
+
+ var tests = [
+ runPropagateDirectorScriptsToChildTest,
+ ].map((testCase) => {
+ return function* () {
+ setup();
+ yield testCase().then(null, (e) => {
+ ok(false, "Exception during testCase run: " + [e, e.fileName, e.lineNumber].join("\n\t"));
+ });
+
+ teardown();
+ };
+ });
+
+ for (var test of tests) {
+ yield test();
+ }
+
+ SimpleTest.finish();
+ });
+};
+
+function setup() {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init(() => true);
+ DebuggerServer.addBrowserActors();
+ SimpleTest.registerCleanupFunction(function() {
+ DebuggerServer.destroy();
+ });
+ }
+}
+
+function teardown() {
+ purgeInstalledDirectorScripts();
+ DebuggerServer.destroy();
+}
+
+/***********************************
+ * test cases
+ **********************************/
+
+function runPropagateDirectorScriptsToChildTest() {
+ let iframe = document.createElement("iframe");
+ iframe.mozbrowser = true;
+
+ document.body.appendChild(iframe);
+
+ return Task.spawn(function* () {
+ var { client, root, transport } = yield newConnectedDebuggerClient();
+
+ var directorRegistryClient = new DirectorRegistryFront(client, root);
+
+ // install a director script
+ yield directorRegistryClient.install("testPropagatedDirectorScript", {
+ scriptCode: "console.log('director script test');",
+ scriptOptions: {}
+ });
+
+ var conn = transport._serverConnection;
+ var childActor = yield DebuggerServer.connectToChild(conn, iframe);
+
+ ok(typeof childActor.directorManagerActor !== "undefined",
+ "childActor.directorActor should be defined");
+
+ var childDirectorManagerClient = new DirectorManagerFront(client, childActor);
+
+ var directorScriptList = yield childDirectorManagerClient.list();
+
+ ok(directorScriptList.installed.length === 1 &&
+ directorScriptList.installed[0] === "testPropagatedDirectorScript",
+ "director scripts propagated correctly")
+
+ yield client.close();
+ });
+}
+ </script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_executeInGlobal-outerized_this.html b/devtools/server/tests/mochitest/test_executeInGlobal-outerized_this.html
new file mode 100644
index 000000000..8bedde618
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_executeInGlobal-outerized_this.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=837060
+
+When we use Debugger.Object.prototype.executeInGlobal, the 'this' value seen
+by the evaluated code should be the WindowProxy, not the inner window
+object.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug 837060</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+ SimpleTest.waitForExplicitFinish();
+
+ var iframe = document.createElement("iframe");
+ iframe.src = "data:text/html,<script>var me = 'page 1';<\/script>";
+ iframe.onload = firstOnLoadHandler;
+ document.body.appendChild(iframe);
+
+ function firstOnLoadHandler() {
+ var dbg = new Debugger;
+ var page1DO = dbg.addDebuggee(iframe.contentWindow);
+ iframe.src = "data:text/html,<script>var me = 'page 2';<\/script>";
+ iframe.onload = function () {
+ var page2DO = dbg.addDebuggee(iframe.contentWindow);
+ ok(page1DO !== page2DO, "the two pages' globals get distinct D.O's");
+ ok(page1DO.unsafeDereference() === page2DO.unsafeDereference(),
+ "unwrapping page1DO and page2DO outerizes both, yielding the same outer window");
+
+ is(page1DO.executeInGlobal('me').return, 'page 1', "page1DO continues to refer to original page");
+ is(page2DO.executeInGlobal('me').return, 'page 2', "page2DO refers to current page");
+
+ is(page1DO.executeInGlobal('this === window').return, true,
+ "page 1: Debugger.Object.prototype.executeInGlobal should outerize 'this'");
+ is(page1DO.executeInGlobalWithBindings('this === window', {x:2}).return, true,
+ "page 1: Debugger.Object.prototype.executeInGlobal should outerize 'this'");
+
+ is(page2DO.executeInGlobal('this === window').return, true,
+ "page 2: Debugger.Object.prototype.executeInGlobal should outerize 'this'");
+ is(page2DO.executeInGlobalWithBindings('this === window', {x:2}).return, true,
+ "page 2: Debugger.Object.prototype.executeInGlobal should outerize 'this'");
+
+ // Debugger doesn't let one use outer windows as globals. You have to innerize.
+ var outerDO = page1DO.makeDebuggeeValue(page1DO.unsafeDereference());
+ ok(outerDO !== page1DO, "outer window gets its own D.O, distinct from page 1's global");
+ ok(outerDO !== page2DO, "outer window gets its own D.O, distinct from page 2's global");
+ SimpleTest.doesThrow(function () { outerDO.executeInGlobal('me'); },
+ "outer window D.Os can't be used as globals");
+
+ SimpleTest.finish();
+ }
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_framerate_01.html b/devtools/server/tests/mochitest/test_framerate_01.html
new file mode 100644
index 000000000..0282d50a2
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_framerate_01.html
@@ -0,0 +1,141 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1007200 - Create a framerate actor
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+window.onload = function() {
+ var Cu = Components.utils;
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var Services = require("Services");
+ var { DebuggerClient } = require("devtools/shared/client/main");
+ var { DebuggerServer } = require("devtools/server/main");
+
+ // Always log packets when running tests.
+ Services.prefs.setBoolPref("devtools.debugger.log", true);
+ SimpleTest.registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+ });
+
+ SimpleTest.waitForExplicitFinish();
+
+ var {FramerateFront} = require("devtools/shared/fronts/framerate");
+
+ function plotFPS(ticks, interval = 100, clamp = 60) {
+ var timeline = [];
+ var totalTicks = ticks.length;
+
+ // If the refresh driver didn't get a chance to tick before the
+ // recording was stopped, assume framerate was 0.
+ if (totalTicks == 0) {
+ timeline.push({ delta: 0, value: 0 });
+ timeline.push({ delta: interval, value: 0 });
+ return timeline;
+ }
+
+ var frameCount = 0;
+ var prevTime = ticks[0];
+
+ for (var i = 1; i < totalTicks; i++) {
+ var currTime = ticks[i];
+ frameCount++;
+
+ var elapsedTime = currTime - prevTime;
+ if (elapsedTime < interval) {
+ continue;
+ }
+
+ var framerate = Math.min(1000 / (elapsedTime / frameCount), clamp);
+ timeline.push({ delta: prevTime, value: framerate });
+ timeline.push({ delta: currTime, value: framerate });
+
+ frameCount = 0;
+ prevTime = currTime;
+ }
+
+ return timeline;
+ };
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(function onListTabs(aResponse) {
+ var form = aResponse.tabs[aResponse.selected];
+ var front = FramerateFront(client, form);
+
+ window.setTimeout(() => {
+ front.startRecording().then(() => {
+ window.setTimeout(() => {
+ front.stopRecording().then(rawData => {
+ onRecordingStopped(front, rawData);
+ });
+ }, 1000);
+ });
+ }, 1000);
+ });
+ });
+
+ function onRecordingStopped(front, rawData) {
+ ok(rawData, "There should be a recording available.");
+
+ var timeline = plotFPS(rawData);
+ ok(timeline.length >= 2,
+ "There should be at least one measurement available, with two entries.");
+
+ var prevTimeStart = timeline[0].delta;
+
+ for (var i = 0; i < timeline.length; i += 2) {
+ var currTimeStart = timeline[i].delta;
+ var currTimeEnd = timeline[i + 1].delta;
+ info("Testing delta: " + currTimeStart + " vs. " + currTimeEnd);
+
+ ok(currTimeStart < currTimeEnd,
+ "The start and end time deltas should be consecutive.");
+ is(currTimeStart, prevTimeStart,
+ "There should be two time deltas for each framerate value.");
+
+ prevTimeStart = currTimeEnd;
+ }
+
+ var prevFramerateValue = -1;
+
+ for (var i = 0; i < timeline.length; i += 2) {
+ var currFramerateStart = timeline[i].value;
+ var currFramerateEnd = timeline[i + 1].value;
+ info("Testing framerate: " + currFramerateStart);
+
+ is(currFramerateStart, currFramerateEnd,
+ "The start and end framerate values should be equal.");
+
+ is(typeof currFramerateStart, "number", "All values should be numbers.");
+ ok(currFramerateStart <= 60, "All values were correctly clamped.")
+
+ prevFramerateValue = currFramerateStart;
+ }
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_framerate_02.html b/devtools/server/tests/mochitest/test_framerate_02.html
new file mode 100644
index 000000000..9d4626b12
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_framerate_02.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1007200 - Create a framerate actor
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+window.onload = function() {
+ var Cu = Components.utils;
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var {DebuggerClient} = require("devtools/shared/client/main");
+ var {DebuggerServer} = require("devtools/server/main");
+ var Services = require("Services");
+
+ // Always log packets when running tests.
+ Services.prefs.setBoolPref("devtools.debugger.log", true);
+ SimpleTest.registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+ });
+
+ SimpleTest.waitForExplicitFinish();
+
+ var {FramerateFront} = require("devtools/shared/fronts/framerate");
+
+ function plotFPS(ticks, interval = 100, clamp = 60) {
+ var timeline = [];
+ var totalTicks = ticks.length;
+
+ // If the refresh driver didn't get a chance to tick before the
+ // recording was stopped, assume framerate was 0.
+ if (totalTicks == 0) {
+ timeline.push({ delta: 0, value: 0 });
+ timeline.push({ delta: interval, value: 0 });
+ return timeline;
+ }
+
+ var frameCount = 0;
+ var prevTime = ticks[0];
+
+ for (var i = 1; i < totalTicks; i++) {
+ var currTime = ticks[i];
+ frameCount++;
+
+ var elapsedTime = currTime - prevTime;
+ if (elapsedTime < interval) {
+ continue;
+ }
+
+ var framerate = Math.min(1000 / (elapsedTime / frameCount), clamp);
+ timeline.push({ delta: prevTime, value: framerate });
+ timeline.push({ delta: currTime, value: framerate });
+
+ frameCount = 0;
+ prevTime = currTime;
+ }
+
+ return timeline;
+ };
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(function onListTabs(aResponse) {
+ var form = aResponse.tabs[aResponse.selected];
+ var front = FramerateFront(client, form);
+
+ front.stopRecording().then(rawData => {
+ ok(rawData, "There should be a recording available.");
+ is(rawData.length, 0, "...but it should be empty.");
+
+ var timeline = plotFPS(rawData);
+ is(timeline.length, 2,
+ "There should be one measurement plotted, with two entries.");
+
+ info("The framerate should be assumed to be 0 if the recording is empty.");
+
+ is(timeline[0].delta, 0,
+ "The first time delta should be 0.");
+ is(timeline[0].value, 0,
+ "The first framerate value should be 0.");
+
+ is(timeline[1].delta, 100,
+ "The last time delta should be 100 (the default interval value).");
+ is(timeline[1].value, 0,
+ "The last framerate value should be 0.");
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ });
+ });
+ });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_framerate_03.html b/devtools/server/tests/mochitest/test_framerate_03.html
new file mode 100644
index 000000000..da76ebc20
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_framerate_03.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1023018 - Tests whether or not the framerate actor can handle time ranges.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+window.onload = function() {
+ var Cu = Components.utils;
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var {DebuggerClient} = require("devtools/shared/client/main");
+ var {DebuggerServer} = require("devtools/server/main");
+ var Services = require("Services");
+
+ // Always log packets when running tests.
+ Services.prefs.setBoolPref("devtools.debugger.log", true);
+ SimpleTest.registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+ });
+
+ SimpleTest.waitForExplicitFinish();
+
+ var {FramerateFront} = require("devtools/shared/fronts/framerate");
+ var START_TICK = 2000;
+ var STOP_TICK = 3000;
+ var TOTAL_TIME = 5000;
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(function onListTabs(aResponse) {
+ var form = aResponse.tabs[aResponse.selected];
+ var front = FramerateFront(client, form);
+
+ front.startRecording().then(() => {
+ window.setTimeout(() => {
+ front.stopRecording(START_TICK, STOP_TICK).then(rawData => {
+ onRecordingStopped(front, rawData);
+ });
+ }, TOTAL_TIME);
+ });
+ });
+ });
+
+ function onRecordingStopped(front, rawData) {
+ ok(rawData, "There should be a recording available.");
+
+ ok(!rawData.find(e => e < START_TICK),
+ "There should be no tick before 2000ms.");
+ ok(!rawData.find(e => e > STOP_TICK),
+ "There should be no tick after 3000ms.");
+
+ for (var tick of rawData) {
+ info("Testing tick: " + tick);
+ is(typeof tick, "number", "All values should be numbers.");
+ }
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ }
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_framerate_04.html b/devtools/server/tests/mochitest/test_framerate_04.html
new file mode 100644
index 000000000..af6747291
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_framerate_04.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1023018 - Tests if the framerate actor keeps recording after navigations.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ var {FramerateFront} = require("devtools/shared/fronts/framerate");
+ var {TargetFactory} = require("devtools/client/framework/target");
+
+ var url = document.getElementById("testContent").href;
+ attachURL(url, onTab);
+
+ function onTab(_, client, form, contentDoc) {
+ var contentWin = contentDoc.defaultView;
+ var chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
+ var selectedTab = chromeWin.gBrowser.selectedTab;
+
+ var target = TargetFactory.forTab(selectedTab);
+ var front = FramerateFront(client, form);
+
+ front.startRecording().then(() => {
+ window.setTimeout(() => {
+ front.getPendingTicks().then(firstBatch => {
+ target.once("will-navigate", () => {
+ window.setTimeout(() => {
+ front.stopRecording().then(secondBatch => {
+ onRecordingStopped(client, firstBatch, secondBatch);
+ });
+ }, 1000);
+ });
+ contentWin.location.reload();
+ });
+ }, 1000);
+ });
+ }
+
+ function onRecordingStopped(client, firstBatch, secondBatch) {
+ ok(firstBatch, "There should be a first batch recording available.");
+ ok(secondBatch, "There should be a second batch recording available.");
+
+ var diff = secondBatch.length - firstBatch.length;
+ info("Difference in ticks: " + diff);
+ ok(diff > 0, "More ticks should be recorded in the second batch.");
+
+ ok(firstBatch.every((e) => secondBatch.indexOf(e) != -1),
+ "All the ticks in the first batch should be in the second batch as well.");
+ ok(secondBatch.every((e, i, array) => i < array.length - 1 ? e < array[i + 1] : true),
+ "All the ticks in the final batch should be ascending in value.");
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ }
+}
+</script>
+</pre>
+<a id="testContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_framerate_05.html b/devtools/server/tests/mochitest/test_framerate_05.html
new file mode 100644
index 000000000..96f56a18f
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_framerate_05.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1034648 - Tests whether a framerate recording can be cancelled.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+window.onload = function() {
+ var Cu = Components.utils;
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var {DebuggerClient} = require("devtools/shared/client/main");
+ var {DebuggerServer} = require("devtools/server/main");
+ var Services = require("Services");
+
+ // Always log packets when running tests.
+ Services.prefs.setBoolPref("devtools.debugger.log", true);
+ SimpleTest.registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+ });
+
+ SimpleTest.waitForExplicitFinish();
+
+ var {FramerateFront} = require("devtools/shared/fronts/framerate");
+
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(function onListTabs(aResponse) {
+ var form = aResponse.tabs[aResponse.selected];
+ var front = FramerateFront(client, form);
+
+ front.startRecording().then(() => {
+ window.setTimeout(() => {
+ front.cancelRecording().then(() => {
+ window.setTimeout(() => {
+ front.getPendingTicks().then(rawTicks => {
+ ok(rawTicks,
+ "The returned pending ticks should be empty (1).");
+ is(rawTicks.length, 0,
+ "The returned pending ticks should be empty (2).");
+
+ front.stopRecording().then(rawData => {
+ ok(rawData,
+ "The returned raw data should be an empty array (1).");
+ is(rawData.length, 0,
+ "The returned raw data should be an empty array (2).");
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ });
+ });
+ }, 1000);
+ });
+ }, 1000);
+ });
+ });
+ });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_framerate_06.html b/devtools/server/tests/mochitest/test_framerate_06.html
new file mode 100644
index 000000000..ecb0a71e0
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_framerate_06.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1171489 - Tests if the framerate actor does not record timestamps from multiple frames.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Framerate actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ var {FramerateFront} = require("devtools/shared/fronts/framerate");
+ var {TargetFactory} = require("devtools/client/framework/target");
+
+ var url = document.getElementById("testContent").href;
+ attachURL(url, onTab);
+
+ function onTab(_, client, form, contentDoc) {
+ var contentWin = contentDoc.defaultView;
+ var chromeWin = Services.wm.getMostRecentWindow("navigator:browser");
+ var selectedTab = chromeWin.gBrowser.selectedTab;
+
+ var target = TargetFactory.forTab(selectedTab);
+ var front = FramerateFront(client, form);
+
+ front.startRecording().then(() => {
+ window.setTimeout(() => {
+ // Wait for the iframe to be loaded again
+ window.addEventListener("message", function loaded (event) {
+ if (event.data === "ready") {
+ window.removeEventListener("message", loaded);
+ window.setTimeout(() => {
+ front.stopRecording().then(ticks => {
+ onRecordingStopped(client, ticks);
+ });
+ }, 1000);
+ }
+ });
+ contentWin.location.reload();
+ }, 1000);
+ });
+ }
+
+ function onRecordingStopped(client, ticks) {
+ var diffs = [];
+
+ info(`Got ${ticks.length} ticks.`);
+
+ for (var i = 1; i < ticks.length; i++) {
+ var prev = ticks[i - 1];
+ var curr = ticks[i];
+ diffs.push(curr - prev);
+ info(curr + " - " + (curr - prev));
+ }
+
+ // 1000 / 60 => 16.666... so we shouldn't get more than diffs of 16.66.. but
+ // when we get ticks from other frames they're usually at diffs of < 1. Sometimes
+ // ticks can still be less than 16ms even on one frame (usually following a very slow
+ // frame), so use a low number (2) to be our threshold
+ var THRESHOLD = 2;
+ ok(ticks.length >= 20, "we should have atleast 20 ticks over the course of two seconds.");
+ var belowThreshold = diffs.filter(v => v <= THRESHOLD);
+ ok(belowThreshold.length <= 10, "we should have very few frames less than the threshold");
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ }
+}
+</script>
+</pre>
+<a id="testContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_getProcess.html b/devtools/server/tests/mochitest/test_getProcess.html
new file mode 100644
index 000000000..3c8ca5727
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_getProcess.html
@@ -0,0 +1,120 @@
+<SDOCTYPv HTM.>
+<html>
+<!--
+Bug 1060093 - Test DebuggerServer.getProcess
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript;version=1.8">
+
+let Cu = Components.utils;
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+
+let {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+let {DebuggerClient} = require("devtools/shared/client/main");
+let {DebuggerServer} = require("devtools/server/main");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ // Always log packets when running tests.
+ ["devtools.debugger.log", true],
+ // Enabled mozbrowser frame to support remote=true
+ ["dom.mozBrowserFramesEnabled", true],
+ // Allows creating a branch new process when creation the iframe
+ ["dom.ipc.processCount", 10],
+ ]
+ }, runTests);
+}
+
+function runTests() {
+ // Instantiate a minimal server
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ }
+ DebuggerServer.allowChromeProcess = true;
+ if (!DebuggerServer.createRootActor) {
+ DebuggerServer.addBrowserActors();
+ }
+
+ let client, iframe, processCount;
+
+ function connect() {
+ // Fake a first connection to the content process
+ let transport = DebuggerServer.connectPipe();
+ client = new DebuggerClient(transport);
+ client.connect().then(listProcess);
+ }
+
+ function listProcess() {
+ // Call listProcesses in order to start receiving new process notifications
+ client.addListener("processListChanged", function listener() {
+ client.removeListener("processListChanged", listener);
+ ok(true, "Received processListChanged event");
+ getProcess();
+ });
+ client.mainRoot.listProcesses(response => {
+ processCount = response.processes.length;
+ // Create a remote iframe to spawn a new process
+ createRemoteIframe();
+ });
+ }
+
+ function createRemoteIframe() {
+ iframe = document.createElement("iframe");
+ iframe.mozbrowser = true;
+ iframe.setAttribute("remote", "true");
+ iframe.setAttribute("src", "data:text/html,foo");
+ document.body.appendChild(iframe);
+ }
+
+ function getProcess() {
+ client.mainRoot.listProcesses(response => {
+ ok(response.processes.length >= 2, "Got at least the parent process and one child");
+ is(response.processes.length, processCount+1 , "Got one additional process on the second call to listProcesses");
+
+ // Connect to the first content processe available
+ let content = response.processes.filter(p => (!p.parent))[0];
+
+ client.getProcess(content.id).then(response => {
+ let actor = response.form;
+ ok(actor.consoleActor, "Got the console actor");
+ ok(actor.chromeDebugger, "Got the thread actor");
+
+ // Ensure sending at least one request to an actor...
+ client.request({
+ to: actor.consoleActor,
+ type: "evaluateJS",
+ text: "var a = 42; a"
+ }, function (response) {
+ ok(response.result, 42, "console.eval worked");
+ cleanup();
+ });
+ });
+ });
+ }
+
+ function cleanup() {
+ client.close().then(function () {
+ DebuggerServer.destroy();
+ iframe.remove();
+ SimpleTest.finish()
+ });
+ }
+
+ connect();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-anonymous.html b/devtools/server/tests/mochitest/test_inspector-anonymous.html
new file mode 100644
index 000000000..56a911c89
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-anonymous.html
@@ -0,0 +1,201 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=777674
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 777674</title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const {InspectorFront} =
+ require("devtools/shared/fronts/inspector");
+ const {_documentWalker} =
+ require("devtools/server/actors/inspector");
+ const nodeFilterConstants =
+ require("devtools/shared/dom-node-filter-constants");
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.webcomponents.enabled", true]
+ ]});
+ SimpleTest.waitForExplicitFinish();
+
+ let gWalker = null;
+ let gClient = null;
+
+ addTest(function setup() {
+ info ("Setting up inspector and walker actors.");
+
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+ });
+
+ addAsyncTest(function* testXBLAnonymousInHTMLDocument() {
+ info ("Testing XBL anonymous in an HTML document.");
+ let rawToolbarbutton = gInspectee.createElementNS(XUL_NS, "toolbarbutton");
+ gInspectee.documentElement.appendChild(rawToolbarbutton);
+
+ let toolbarbutton = yield gWalker.querySelector(gWalker.rootNode, "toolbarbutton");
+ let children = yield gWalker.children(toolbarbutton);
+
+ is (toolbarbutton.numChildren, 0, "XBL content is not visible in HTML doc");
+ is (children.nodes.length, 0, "XBL content is not returned in HTML doc");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testNativeAnonymous() {
+ info ("Testing native anonymous content with walker.");
+
+ let select = yield gWalker.querySelector(gWalker.rootNode, "select");
+ let children = yield gWalker.children(select);
+
+ is (select.numChildren, 2, "No native anon content for form control");
+ is (children.nodes.length, 2, "No native anon content for form control");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testNativeAnonymousStartingNode() {
+ info ("Tests attaching an element that a walker can't see.");
+
+ let serverWalker = DebuggerServer._searchAllConnectionsForActor(gWalker.actorID);
+ let docwalker = new _documentWalker(
+ gInspectee.querySelector("select"),
+ gInspectee.defaultView,
+ nodeFilterConstants.SHOW_ALL,
+ () => {
+ return nodeFilterConstants.FILTER_ACCEPT
+ }
+ );
+ let scrollbar = docwalker.lastChild();
+ is (scrollbar.tagName, "scrollbar", "An anonymous child has been fetched");
+
+ let node = yield serverWalker.attachElement(scrollbar);
+
+ ok (node, "A response has arrived");
+ ok (node.node, "A node is in the response");
+ is (node.node.rawNode.tagName, "SELECT",
+ "The node has changed to a parent that the walker recognizes");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testPseudoElements() {
+ info ("Testing pseudo elements with walker.");
+
+ // Markup looks like: <div><::before /><span /><::after /></div>
+ let pseudo = yield gWalker.querySelector(gWalker.rootNode, "#pseudo");
+ let children = yield gWalker.children(pseudo);
+
+ is (pseudo.numChildren, 1, "::before/::after are not counted if there is a child");
+ is (children.nodes.length, 3, "Correct number of children");
+
+ let before = children.nodes[0];
+ ok (before.isAnonymous, "Child is anonymous");
+ ok (!before._form.isXBLAnonymous, "Child is not XBL anonymous");
+ ok (!before._form.isShadowAnonymous, "Child is not shadow anonymous");
+ ok (before._form.isNativeAnonymous, "Child is native anonymous");
+
+ let span = children.nodes[1];
+ ok (!span.isAnonymous, "Child is not anonymous");
+
+ let after = children.nodes[2];
+ ok (after.isAnonymous, "Child is anonymous");
+ ok (!after._form.isXBLAnonymous, "Child is not XBL anonymous");
+ ok (!after._form.isShadowAnonymous, "Child is not shadow anonymous");
+ ok (after._form.isNativeAnonymous, "Child is native anonymous");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testEmptyWithPseudo() {
+ info ("Testing elements with no childrent, except for pseudos.");
+
+ info ("Checking an element whose only child is a pseudo element");
+ let pseudo = yield gWalker.querySelector(gWalker.rootNode, "#pseudo-empty");
+ let children = yield gWalker.children(pseudo);
+
+ is (pseudo.numChildren, 1, "::before/::after are is counted if there are no other children");
+ is (children.nodes.length, 1, "Correct number of children");
+
+ let before = children.nodes[0];
+ ok (before.isAnonymous, "Child is anonymous");
+ ok (!before._form.isXBLAnonymous, "Child is not XBL anonymous");
+ ok (!before._form.isShadowAnonymous, "Child is not shadow anonymous");
+ ok (before._form.isNativeAnonymous, "Child is native anonymous");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testShadowAnonymous() {
+ info ("Testing shadow DOM content.");
+
+ let shadow = yield gWalker.querySelector(gWalker.rootNode, "#shadow");
+ let children = yield gWalker.children(shadow);
+
+ is (shadow.numChildren, 3, "Children of the shadow root are counted");
+ is (children.nodes.length, 3, "Children returned from walker");
+
+ let before = children.nodes[0];
+ ok (before.isAnonymous, "Child is anonymous");
+ ok (!before._form.isXBLAnonymous, "Child is not XBL anonymous");
+ ok (!before._form.isShadowAnonymous, "Child is not shadow anonymous");
+ ok (before._form.isNativeAnonymous, "Child is native anonymous");
+
+ // <h3>Shadow <em>DOM</em></h3>
+ let shadowChild1 = children.nodes[1];
+ ok (shadowChild1.isAnonymous, "Child is anonymous");
+ ok (!shadowChild1._form.isXBLAnonymous, "Child is not XBL anonymous");
+ ok (shadowChild1._form.isShadowAnonymous, "Child is shadow anonymous");
+ ok (!shadowChild1._form.isNativeAnonymous, "Child is not native anonymous");
+
+ let shadowSubChildren = yield gWalker.children(children.nodes[1]);
+ is (shadowChild1.numChildren, 2, "Subchildren of the shadow root are counted");
+ is (shadowSubChildren.nodes.length, 2, "Subchildren are returned from walker");
+
+ // <em>DOM</em>
+ let shadowSubChild = children.nodes[1];
+ ok (shadowSubChild.isAnonymous, "Child is anonymous");
+ ok (!shadowSubChild._form.isXBLAnonymous, "Child is not XBL anonymous");
+ ok (shadowSubChild._form.isShadowAnonymous, "Child is shadow anonymous");
+ ok (!shadowSubChild._form.isNativeAnonymous, "Child is not native anonymous");
+
+ // <select multiple></select>
+ let shadowChild2 = children.nodes[2];
+ ok (shadowChild2.isAnonymous, "Child is anonymous");
+ ok (!shadowChild2._form.isXBLAnonymous, "Child is not XBL anonymous");
+ ok (shadowChild2._form.isShadowAnonymous, "Child is shadow anonymous");
+ ok (!shadowChild2._form.isNativeAnonymous, "Child is not native anonymous");
+
+ runNextTest();
+ });
+
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-changeattrs.html b/devtools/server/tests/mochitest/test_inspector-changeattrs.html
new file mode 100644
index 000000000..23b7660d2
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-changeattrs.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gClient = null;
+var gWalker = null;
+var checkActorIDs = [];
+
+function assertOwnership() {
+ assertOwnershipTrees(gWalker);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testChangeAttrs() {
+ let attrNode = gInspectee.querySelector("#a");
+ let attrFront;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => {
+ attrFront = front;
+ dump("attrFront is: " + attrFront + "\n");
+ // Add a few attributes.
+ let list = attrFront.startModifyingAttributes();
+ list.setAttribute("data-newattr", "newvalue");
+ list.setAttribute("data-newattr2", "newvalue");
+ return list.apply();
+ }).then(() => {
+ // We're only going to test that the change hit the document.
+ // There are other tests that make sure changes are propagated
+ // to the client.
+ is(attrNode.getAttribute("data-newattr"), "newvalue", "Node should have the first new attribute");
+ is(attrNode.getAttribute("data-newattr2"), "newvalue", "Node should have the second new attribute.");
+ }).then(() => {
+ // Change an attribute.
+ let list = attrFront.startModifyingAttributes();
+ list.setAttribute("data-newattr", "changedvalue");
+ return list.apply();
+ }).then(() => {
+ is(attrNode.getAttribute("data-newattr"), "changedvalue", "Node should have the changed first value.");
+ is(attrNode.getAttribute("data-newattr2"), "newvalue", "Second value should remain unchanged.");
+ }).then(() => {
+ let list = attrFront.startModifyingAttributes();
+ list.removeAttribute("data-newattr2");
+ return list.apply();
+ }).then(() => {
+ is(attrNode.getAttribute("data-newattr"), "changedvalue", "Node should have the changed first value.");
+ ok(!attrNode.hasAttribute("data-newattr2"), "Second value should be removed.");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gInspectee;
+ delete gClient;
+ runNextTest();
+});
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-changevalue.html b/devtools/server/tests/mochitest/test_inspector-changevalue.html
new file mode 100644
index 000000000..a5b613157
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-changevalue.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const Ci = Components.interfaces;
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gClient = null;
+var gWalker = null;
+
+function assertOwnership() {
+ assertOwnershipTrees(gWalker);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testChangeValue() {
+ let contentNode = gInspectee.querySelector("#a").firstChild;
+ let nodeFront;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => {
+ // Get the text child
+ return gWalker.children(front, { maxNodes: 1 });
+ }).then(children => {
+ nodeFront = children.nodes[0];
+ is(nodeFront.nodeType, Ci.nsIDOMNode.TEXT_NODE);
+ return nodeFront.setNodeValue("newvalue");
+ }).then(() => {
+ // We're only going to test that the change hit the document.
+ // There are other tests that make sure changes are propagated
+ // to the client.
+ is(contentNode.nodeValue, "newvalue", "Node should have a new value.");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gInspectee;
+ delete gClient;
+ runNextTest();
+});
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-dead-nodes.html b/devtools/server/tests/mochitest/test_inspector-dead-nodes.html
new file mode 100644
index 000000000..274636cd6
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-dead-nodes.html
@@ -0,0 +1,386 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1121528
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1121528</title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker, gDoc;
+
+addAsyncTest(function() {
+ let url = document.getElementById("inspectorContent").href;
+
+ let def = promise.defer();
+ attachURL(url, function(err, client, tab, doc) {
+ def.resolve({client, tab, doc});
+ });
+ let {client, tab, doc} = yield def.promise;
+ gDoc = doc;
+
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ gWalker = yield inspector.getWalker();
+
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.parents(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.parents(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.parents() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.children(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "body");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.children(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.children() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.siblings(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.siblings(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.siblings() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.nextSibling(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.nextSibling(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.nextSibling() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.previousSibling(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.previousSibling(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.previousSibling() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.addPseudoClassLock(nodeFront) before the load completes " +
+ "shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.addPseudoClassLock(nodeFront, ":hover");
+ yield newRoot;
+
+ ok(true, "The call to walker.addPseudoClassLock() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.removePseudoClassLock(nodeFront) before the load completes " +
+ "shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.removePseudoClassLock(nodeFront, ":hover");
+ yield newRoot;
+
+ ok(true, "The call to walker.removePseudoClassLock() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.clearPseudoClassLocks(nodeFront) before the load completes " +
+ "shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.clearPseudoClassLocks(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.clearPseudoClassLocks() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.innerHTML(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.innerHTML(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.innerHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.setInnerHTML(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.setInnerHTML(nodeFront, "<span>innerHTML changed</span>");
+ yield newRoot;
+
+ ok(true, "The call to walker.setInnerHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.outerHTML(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.outerHTML(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.outerHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.setOuterHTML(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.setOuterHTML(nodeFront, "<h1><span>innerHTML changed</span></h1>");
+ yield newRoot;
+
+ ok(true, "The call to walker.setOuterHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.insertAdjacentHTML(nodeFront) before the load completes shouldn't " +
+ "fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.insertAdjacentHTML(nodeFront, "afterEnd",
+ "<span>new adjacent HTML</span>");
+ yield newRoot;
+
+ ok(true, "The call to walker.insertAdjacentHTML() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.removeNode(nodeFront) before the load completes should throw");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ let hasThrown = false;
+ try {
+ yield gWalker.removeNode(nodeFront);
+ } catch (e) {
+ hasThrown = true;
+ }
+ yield newRoot;
+
+ ok(hasThrown, "The call to walker.removeNode() threw");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.removeNodes([nodeFront]) before the load completes should throw");
+
+ let nodeFront1 = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let nodeFront2 = yield gWalker.querySelector(gWalker.rootNode, "#longstring");
+ let nodeFront3 = yield gWalker.querySelector(gWalker.rootNode, "#shortstring");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ let hasThrown = false;
+ try {
+ yield gWalker.removeNodes([nodeFront1, nodeFront2, nodeFront3]);
+ } catch (e) {
+ hasThrown = true;
+ }
+ yield newRoot;
+
+ ok(hasThrown, "The call to walker.removeNodes() threw");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.insertBefore(nodeFront, parent, null) before the load completes " +
+ "shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newParentFront = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.insertBefore(nodeFront, newParentFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.insertBefore() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.insertBefore(nodeFront, parent, sibling) before the load completes " +
+ "shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newParentFront = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
+ let siblingFront = yield gWalker.querySelector(gWalker.rootNode, "#b");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.insertBefore(nodeFront, newParentFront, siblingFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.insertBefore() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.editTagName(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.editTagName(nodeFront, "h2");
+ yield newRoot;
+
+ ok(true, "The call to walker.editTagName() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.hideNode(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.hideNode(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.hideNode() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.unhideNode(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.unhideNode(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.unhideNode() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.releaseNode(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "h1");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.releaseNode(nodeFront);
+ yield newRoot;
+
+ ok(true, "The call to walker.releaseNode() didn't fail");
+ runNextTest();
+});
+
+addAsyncTest(function() {
+ info("Getting a nodeFront, reloading the page, and calling " +
+ "walker.querySelector(nodeFront) before the load completes shouldn't fail");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, "body");
+ let newRoot = waitForMutation(gWalker, isNewRoot);
+ gDoc.defaultView.location.reload();
+ yield gWalker.querySelector(nodeFront, "h1");
+ yield newRoot;
+
+ ok(true, "The call to walker.querySelector() didn't fail");
+ runNextTest();
+});
+
+addTest(function cleanup() {
+ gWalker = gDoc = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=932937">Mozilla Bug 1121528</a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-duplicate-node.html b/devtools/server/tests/mochitest/test_inspector-duplicate-node.html
new file mode 100644
index 000000000..35722f226
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-duplicate-node.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1208864
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1208864</title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gClient = null;
+var gWalker = null;
+
+function assertOwnership() {
+ assertOwnershipTrees(gWalker);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(Task.async(function* testDuplicateNode() {
+ let className = ".node-to-duplicate";
+ let matches = yield gWalker.querySelectorAll(gWalker.rootNode, className);
+ is(matches.length, 1, "There should initially be one node to duplicate.");
+
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, className);
+ yield gWalker.duplicateNode(nodeFront);
+
+ matches = yield gWalker.querySelectorAll(gWalker.rootNode, className);
+ is(matches.length, 2, "The node should now be duplicated.");
+
+ runNextTest();
+}));
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gInspectee;
+ delete gClient;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1208864">Mozilla Bug 1208864</a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-hide.html b/devtools/server/tests/mochitest/test_inspector-hide.html
new file mode 100644
index 000000000..d9b134c22
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-hide.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gClient = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testRearrange() {
+ let listFront = null;
+ let listNode = gInspectee.querySelector("#longlist");
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(front => {
+ listFront = front;
+ }).then(() => {
+ let computed = gInspectee.defaultView.getComputedStyle(listNode);
+ ok(computed.visibility, "visible", "Node should be visible to start with");
+ return gWalker.hideNode(listFront);
+ }).then(response => {
+ let computed = gInspectee.defaultView.getComputedStyle(listNode);
+ ok(computed.visibility, "hidden", "Node should be hidden");
+ return gWalker.unhideNode(listFront);
+ }).then(() => {
+ let computed = gInspectee.defaultView.getComputedStyle(listNode);
+ ok(computed.visibility, "visible", "Node should be visible again.");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gClient;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-insert.html b/devtools/server/tests/mochitest/test_inspector-insert.html
new file mode 100644
index 000000000..82b4fef3e
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-insert.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gClient = null;
+
+function assertOwnership() {
+ return assertOwnershipTrees(gWalker);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addAsyncTest(function* testRearrange() {
+ let longlist = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
+ let children = yield gWalker.children(longlist);
+ let nodeA = children.nodes[0];
+ is(nodeA.id, "a", "Got the expected node.");
+
+ // Move nodeA to the end of the list.
+ yield gWalker.insertBefore(nodeA, longlist, null);
+ ok(!gInspectee.querySelector("#a").nextSibling, "a should now be at the end of the list.");
+ children = yield gWalker.children(longlist);
+ is(nodeA, children.nodes[children.nodes.length - 1], "a should now be the last returned child.");
+
+ // Now move it to the middle of the list.
+ let nextNode = children.nodes[13];
+ yield gWalker.insertBefore(nodeA, longlist, nextNode);
+ let sibling =
+ new inspector._documentWalker(gInspectee.querySelector("#a"), window).nextSibling();
+ is(sibling, nextNode.rawNode(), "Node should match the expected next node.");
+ children = yield gWalker.children(longlist);
+ is(nodeA, children.nodes[13], "a should be where we expect it.");
+ is(nextNode, children.nodes[14], "next node should be where we expect it.");
+
+ runNextTest();
+});
+
+addAsyncTest(function* testInsertInvalidInput() {
+ let longlist = yield gWalker.querySelector(gWalker.rootNode, "#longlist");
+ let children = yield gWalker.children(longlist);
+ let nodeA = children.nodes[0];
+ let nextSibling = children.nodes[1];
+
+ // Now move it to the original location and make sure no mutation happens.
+ let hasMutated = false;
+ let observer = new gInspectee.defaultView.MutationObserver(() => {
+ hasMutated = true;
+ });
+ observer.observe(longlist.rawNode(), {
+ childList: true,
+ });
+
+ yield gWalker.insertBefore(nodeA, longlist, nodeA);
+ ok(!hasMutated, "hasn't mutated");
+ hasMutated = false;
+
+ yield gWalker.insertBefore(nodeA, longlist, nextSibling);
+ ok(!hasMutated, "still hasn't mutated after inserting before nextSibling");
+ hasMutated = false;
+
+ yield gWalker.insertBefore(nodeA, longlist);
+ ok(hasMutated, "has mutated after inserting with null sibling");
+ hasMutated = false;
+
+ yield gWalker.insertBefore(nodeA, longlist);
+ ok(!hasMutated, "hasn't mutated after inserting with null sibling again");
+
+ observer.disconnect();
+ runNextTest();
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gClient;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-mutations-attr.html b/devtools/server/tests/mochitest/test_inspector-mutations-attr.html
new file mode 100644
index 000000000..15c14608b
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-mutations-attr.html
@@ -0,0 +1,167 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gWalker = null;
+var gClient = null;
+var attrNode;
+var attrFront;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(setupAttrTest);
+addTest(testAddAttribute);
+addTest(testChangeAttribute);
+addTest(testRemoveAttribute);
+addTest(testQueuedMutations);
+addTest(setupFrameAttrTest);
+addTest(testAddAttribute);
+addTest(testChangeAttribute);
+addTest(testRemoveAttribute);
+addTest(testQueuedMutations);
+
+function setupAttrTest() {
+ attrNode = gInspectee.querySelector("#a")
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(node => {
+ attrFront = node;
+ }).then(runNextTest));
+}
+
+function setupFrameAttrTest() {
+ let frame = gInspectee.querySelector('#childFrame');
+ attrNode = frame.contentDocument.querySelector("#a");
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => {
+ return gWalker.children(childFrame);
+ }).then(children => {
+ let nodes = children.nodes;
+ ok(nodes.length, 1, "There should be only one child of the iframe");
+ is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
+ return gWalker.querySelector(nodes[0], "#a");
+ }).then(node => {
+ attrFront = node;
+ }).then(runNextTest));
+}
+
+function testAddAttribute() {
+ attrNode.setAttribute("data-newattr", "newvalue");
+ attrNode.setAttribute("data-newattr2", "newvalue");
+ gWalker.once("mutations", () => {
+ is(attrFront.attributes.length, 3, "Should have id and two new attributes.");
+ is(attrFront.getAttribute("data-newattr"), "newvalue", "Node front should have the first new attribute");
+ is(attrFront.getAttribute("data-newattr2"), "newvalue", "Node front should have the second new attribute.");
+ runNextTest();
+ });
+}
+
+function testChangeAttribute() {
+ attrNode.setAttribute("data-newattr", "changedvalue1");
+ attrNode.setAttribute("data-newattr", "changedvalue2");
+ attrNode.setAttribute("data-newattr", "changedvalue3");
+ gWalker.once("mutations", mutations => {
+ is(mutations.length, 1, "Only one mutation is sent for multiple queued attribute changes");
+ is(attrFront.attributes.length, 3, "Should have id and two new attributes.");
+ is(attrFront.getAttribute("data-newattr"), "changedvalue3", "Node front should have the changed first value");
+ is(attrFront.getAttribute("data-newattr2"), "newvalue", "Second value should remain unchanged.");
+ runNextTest();
+ });
+}
+
+function testRemoveAttribute() {
+ attrNode.removeAttribute("data-newattr2");
+ gWalker.once("mutations", () => {
+ is(attrFront.attributes.length, 2, "Should have id and one remaining attribute.");
+ is(attrFront.getAttribute("data-newattr"), "changedvalue3", "Node front should still have the first value");
+ ok(!attrFront.hasAttribute("data-newattr2"), "Second value should be removed.");
+ runNextTest();
+ })
+}
+
+function testQueuedMutations() {
+ // All modifications to each attribute should be queued in one mutation event.
+
+ attrNode.removeAttribute("data-newattr");
+ attrNode.setAttribute("data-newattr", "1");
+ attrNode.removeAttribute("data-newattr");
+ attrNode.setAttribute("data-newattr", "2");
+ attrNode.removeAttribute("data-newattr");
+
+ for (var i = 0; i <= 1000; i++) {
+ attrNode.setAttribute("data-newattr2", i);
+ }
+
+ attrNode.removeAttribute("data-newattr3");
+ attrNode.setAttribute("data-newattr3", "1");
+ attrNode.removeAttribute("data-newattr3");
+ attrNode.setAttribute("data-newattr3", "2");
+ attrNode.removeAttribute("data-newattr3");
+ attrNode.setAttribute("data-newattr3", "3");
+
+ // This shouldn't be added in the attribute set, since it's a new
+ // attribute that's been added and removed.
+ attrNode.setAttribute("data-newattr4", "4");
+ attrNode.removeAttribute("data-newattr4");
+
+ gWalker.once("mutations", mutations => {
+ is(mutations.length, 4, "Only one mutation each is sent for multiple queued attribute changes");
+ is(attrFront.attributes.length, 3, "Should have id, data-newattr2, and data-newattr3.");
+
+ is(attrFront.getAttribute("data-newattr2"), "1000", "Node front should still have the correct value");
+ is(attrFront.getAttribute("data-newattr3"), "3", "Node front should still have the correct value");
+ ok(!attrFront.hasAttribute("data-newattr"), "Attribute value should be removed.");
+ ok(!attrFront.hasAttribute("data-newattr4"), "Attribute value should be removed.");
+
+ runNextTest();
+ })
+}
+
+addTest(function cleanup() {
+ delete gInspectee;
+ delete gWalker;
+ delete gClient;
+ runNextTest();
+});
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-mutations-childlist.html b/devtools/server/tests/mochitest/test_inspector-mutations-childlist.html
new file mode 100644
index 000000000..d845b987e
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-mutations-childlist.html
@@ -0,0 +1,310 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gWalker = null;
+var gClient = null;
+var gCleanupConnection = null;
+
+function setup(callback) {
+ let url = document.getElementById("inspectorContent").href;
+ gCleanupConnection = attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ gClient = client;
+ gWalker = walker;
+ }).then(callback));
+ });
+}
+
+function teardown() {
+ gWalker = null;
+ gClient = null;
+ gInspectee = null;
+ if (gCleanupConnection) {
+ gCleanupConnection();
+ gCleanupConnection = null;
+ }
+}
+
+function assertOwnership() {
+ let num = assertOwnershipTrees(gWalker);
+}
+
+function setParent(nodeSelector, newParentSelector) {
+ let node = gInspectee.querySelector(nodeSelector);
+ if (newParentSelector) {
+ let newParent = gInspectee.querySelector(newParentSelector);
+ newParent.appendChild(node);
+ } else {
+ node.parentNode.removeChild(node);
+ }
+}
+
+function loadSelector(selector) {
+ return gWalker.querySelectorAll(gWalker.rootNode, selector).then(nodeList => {
+ return nodeList.items();
+ });
+}
+
+function loadSelectors(selectors) {
+ return promise.all(Array.from(selectors, (sel) => loadSelector(sel)));
+}
+
+function doMoves(moves) {
+ for (let move of moves) {
+ setParent(move[0], move[1]);
+ }
+}
+
+/**
+ * Test a set of tree rearrangements and make sure they cause the expected changes.
+ */
+
+var gDummySerial = 0;
+
+function mutationTest(testSpec) {
+ return function() {
+ setup(() => {
+ promiseDone(loadSelectors(testSpec.load || ["html"]).then(() => {
+ gWalker.autoCleanup = !!testSpec.autoCleanup;
+ if (testSpec.preCheck) {
+ testSpec.preCheck();
+ }
+ doMoves(testSpec.moves || []);
+
+ // Some of these moves will trigger no mutation events,
+ // so do a dummy change to the root node to trigger
+ // a mutation event anyway.
+ gInspectee.documentElement.setAttribute("data-dummy", gDummySerial++);
+
+ gWalker.once("mutations", (mutations) => {
+ // Filter out our dummy mutation.
+ mutations = mutations.filter(change => {
+ if (change.type == "attributes" &&
+ change.attributeName == "data-dummy") {
+ return false;
+ }
+ return true;
+ });
+ assertOwnership();
+ if (testSpec.postCheck) {
+ testSpec.postCheck(mutations);
+ }
+ teardown();
+ runNextTest();
+ });
+ }));
+ })
+ }
+}
+
+// Verify that our dummy mutation works.
+addTest(mutationTest({
+ autoCleanup: false,
+ postCheck: function(mutations) {
+ is(mutations.length, 0, "Dummy mutation is filtered out.");
+ }
+}));
+
+// Test a simple move to a different location in the sibling list for the same
+// parent.
+addTest(mutationTest({
+ autoCleanup: false,
+ load: ["#longlist div"],
+ moves: [
+ ["#a", "#longlist"]
+ ],
+ postCheck: function(mutations) {
+ let remove = mutations[0];
+ is(remove.type, "childList", "First mutation should be a childList.")
+ ok(remove.removed.length > 0, "First mutation should be a removal.")
+ let add = mutations[1];
+ is(add.type, "childList", "Second mutation should be a childList removal.")
+ ok(add.added.length > 0, "Second mutation should be an addition.")
+ let a = add.added[0];
+ is(a.id, "a", "Added node should be #a");
+ is(a.parentNode(), remove.target, "Should still be a child of longlist.");
+ is(remove.target, add.target, "First and second mutations should be against the same node.");
+ }
+}));
+
+// Test a move to another location that is within our ownership tree.
+addTest(mutationTest({
+ autoCleanup: false,
+ load: ["#longlist div", "#longlist-sibling"],
+ moves: [
+ ["#a", "#longlist-sibling"]
+ ],
+ postCheck: function(mutations) {
+ let remove = mutations[0];
+ is(remove.type, "childList", "First mutation should be a childList.")
+ ok(remove.removed.length > 0, "First mutation should be a removal.")
+ let add = mutations[1];
+ is(add.type, "childList", "Second mutation should be a childList removal.")
+ ok(add.added.length > 0, "Second mutation should be an addition.")
+ let a = add.added[0];
+ is(a.id, "a", "Added node should be #a");
+ is(a.parentNode(), add.target, "Should still be a child of longlist.");
+ is(add.target.id, "longlist-sibling", "long-sibling should be the target.");
+ }
+}));
+
+// Move an unseen node with a seen parent into our ownership tree - should generate a
+// childList pair with no adds or removes.
+addTest(mutationTest({
+ autoCleanup: false,
+ load: ["#longlist"],
+ moves: [
+ ["#longlist-sibling", "#longlist"]
+ ],
+ postCheck: function(mutations) {
+ is(mutations.length, 2, "Should generate two mutations");
+ is(mutations[0].type, "childList", "Should be childList mutations.");
+ is(mutations[0].added.length, 0, "Should have no adds.");
+ is(mutations[0].removed.length, 0, "Should have no removes.");
+ is(mutations[1].type, "childList", "Should be childList mutations.");
+ is(mutations[1].added.length, 0, "Should have no adds.");
+ is(mutations[1].removed.length, 0, "Should have no removes.");
+ }
+}));
+
+// Move an unseen node with an unseen parent into our ownership tree. Should only
+// generate one childList mutation with no adds or removes.
+addTest(mutationTest({
+ autoCleanup: false,
+ load: ["#longlist div"],
+ moves: [
+ ["#longlist-sibling-firstchild", "#longlist"]
+ ],
+ postCheck: function(mutations) {
+ is(mutations.length, 1, "Should generate two mutations");
+ is(mutations[0].type, "childList", "Should be childList mutations.");
+ is(mutations[0].added.length, 0, "Should have no adds.");
+ is(mutations[0].removed.length, 0, "Should have no removes.");
+ }
+}));
+
+// Move a node between unseen nodes, should generate no mutations.
+addTest(mutationTest({
+ autoCleanup: false,
+ load: ["html"],
+ moves: [
+ ["#longlist-sibling", "#longlist"]
+ ],
+ postCheck: function(mutations) {
+ is(mutations.length, 0, "Should generate no mutations.");
+ }
+}));
+
+// Orphan a node and don't clean it up
+addTest(mutationTest({
+ autoCleanup: false,
+ load: ["#longlist div"],
+ moves: [
+ ["#longlist", null]
+ ],
+ postCheck: function(mutations) {
+ is(mutations.length, 1, "Should generate one mutation.");
+ let change = mutations[0];
+ is(change.type, "childList", "Should be a childList.");
+ is(change.removed.length, 1, "Should have removed a child.");
+ let ownership = clientOwnershipTree(gWalker);
+ is(ownership.orphaned.length, 1, "Should have one orphaned subtree.");
+ is(ownershipTreeSize(ownership.orphaned[0]), 1 + 26 + 26, "Should have orphaned longlist, and 26 children, and 26 singleTextChilds");
+ }
+}));
+
+// Orphan a node, and do clean it up.
+addTest(mutationTest({
+ autoCleanup: true,
+ load: ["#longlist div"],
+ moves: [
+ ["#longlist", null]
+ ],
+ postCheck: function(mutations) {
+ is(mutations.length, 1, "Should generate one mutation.");
+ let change = mutations[0];
+ is(change.type, "childList", "Should be a childList.");
+ is(change.removed.length, 1, "Should have removed a child.");
+ let ownership = clientOwnershipTree(gWalker);
+ is(ownership.orphaned.length, 0, "Should have no orphaned subtrees.");
+ }
+}));
+
+// Orphan a node by moving it into the tree but out of our visible subtree.
+addTest(mutationTest({
+ autoCleanup: false,
+ load: ["#longlist div"],
+ moves: [
+ ["#longlist", "#longlist-sibling"]
+ ],
+ postCheck: function(mutations) {
+ is(mutations.length, 1, "Should generate one mutation.");
+ let change = mutations[0];
+ is(change.type, "childList", "Should be a childList.");
+ is(change.removed.length, 1, "Should have removed a child.");
+ let ownership = clientOwnershipTree(gWalker);
+ is(ownership.orphaned.length, 1, "Should have one orphaned subtree.");
+ is(ownershipTreeSize(ownership.orphaned[0]), 1 + 26 + 26, "Should have orphaned longlist, 26 children, and 26 singleTextChilds.");
+ }
+}));
+
+// Orphan a node by moving it into the tree but out of our visible subtree, and clean it up.
+addTest(mutationTest({
+ autoCleanup: true,
+ load: ["#longlist div"],
+ moves: [
+ ["#longlist", "#longlist-sibling"]
+ ],
+ postCheck: function(mutations) {
+ is(mutations.length, 1, "Should generate one mutation.");
+ let change = mutations[0];
+ is(change.type, "childList", "Should be a childList.");
+ is(change.removed.length, 1, "Should have removed a child.");
+ let ownership = clientOwnershipTree(gWalker);
+ is(ownership.orphaned.length, 0, "Should have no orphaned subtrees.");
+ }
+}));
+
+
+addTest(function cleanup() {
+ delete gInspectee;
+ delete gWalker;
+ delete gClient;
+ runNextTest();
+});
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-mutations-events.html b/devtools/server/tests/mochitest/test_inspector-mutations-events.html
new file mode 100644
index 000000000..992bc7f8d
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-mutations-events.html
@@ -0,0 +1,183 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1157469
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1157469</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+
+window.onload = function() {
+
+ const Cu = Components.utils;
+ const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const {InspectorFront} = require("devtools/shared/fronts/inspector");
+
+ SimpleTest.waitForExplicitFinish();
+
+ let inspectee = null;
+ let inspector = null;
+ let walker = null;
+ let eventListener1 = function () {};
+ let eventListener2 = function () {};
+ let eventNode1;
+ let eventNode2;
+ let eventFront1;
+ let eventFront2;
+
+ addAsyncTest(function* setup() {
+ info ("Setting up inspector and walker actors.");
+ let url = document.getElementById("inspectorContent").href;
+
+ yield new Promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ inspectee = doc;
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ walker = yield inspector.getWalker();
+ ok(walker, "getWalker() should return an actor.");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* setupEventTest() {
+ eventNode1 = inspectee.querySelector("#a")
+ eventNode2 = inspectee.querySelector("#b")
+
+ eventFront1 = yield walker.querySelector(walker.rootNode, "#a");
+ eventFront2 = yield walker.querySelector(walker.rootNode, "#b");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testChangeEventListenerOnSingleNode() {
+ checkNodesHaveNoEventListener();
+
+ info("add event listener on a single node");
+ eventNode1.addEventListener("click", eventListener1);
+
+ let mutations = yield waitForMutations();
+ is(mutations.length, 1, "one mutation expected");
+ is(mutations[0].target, eventFront1, "mutation targets eventFront1");
+ is(mutations[0].type, "events", "mutation type is events");
+ is(mutations[0].hasEventListeners, true, "mutation target should have event listeners");
+ is(eventFront1.hasEventListeners, true, "eventFront1 should have event listeners");
+
+ info("remove event listener on a single node");
+ eventNode1.removeEventListener("click", eventListener1);
+
+ mutations = yield waitForMutations();
+ is(mutations.length, 1, "one mutation expected");
+ is(mutations[0].target, eventFront1, "mutation targets eventFront1");
+ is(mutations[0].type, "events", "mutation type is events");
+ is(mutations[0].hasEventListeners, false, "mutation target should have no event listeners");
+ is(eventFront1.hasEventListeners, false, "eventFront1 should have no event listeners");
+
+ info("perform several event listener changes on a single node")
+ eventNode1.addEventListener("click", eventListener1);
+ eventNode1.addEventListener("click", eventListener2);
+ eventNode1.removeEventListener("click", eventListener1);
+ eventNode1.removeEventListener("click", eventListener2);
+
+ mutations = yield waitForMutations();
+ is(mutations.length, 1, "one mutation expected");
+ is(mutations[0].target, eventFront1, "mutation targets eventFront1");
+ is(mutations[0].type, "events", "mutation type is events");
+ is(mutations[0].hasEventListeners, false, "no event listener expected on mutation target");
+ is(eventFront1.hasEventListeners, false, "no event listener expected on node");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testChangeEventsOnSeveralNodes() {
+ checkNodesHaveNoEventListener();
+
+ info("add event listeners on both nodes");
+ eventNode1.addEventListener("click", eventListener1);
+ eventNode2.addEventListener("click", eventListener2);
+
+ let mutations = yield waitForMutations();
+ is(mutations.length, 2, "two mutations expected, one for each modified node");
+ // first mutation
+ is(mutations[0].target, eventFront1, "first mutation targets eventFront1");
+ is(mutations[0].type, "events", "mutation type is events");
+ is(mutations[0].hasEventListeners, true, "mutation target should have event listeners");
+ is(eventFront1.hasEventListeners, true, "eventFront1 should have event listeners");
+ // second mutation
+ is(mutations[1].target, eventFront2, "second mutation targets eventFront2");
+ is(mutations[1].type, "events", "mutation type is events");
+ is(mutations[1].hasEventListeners, true, "mutation target should have event listeners");
+ is(eventFront2.hasEventListeners, true, "eventFront1 should have event listeners");
+
+ info("remove event listeners on both nodes");
+ eventNode1.removeEventListener("click", eventListener1);
+ eventNode2.removeEventListener("click", eventListener2);
+
+ mutations = yield waitForMutations();
+ is(mutations.length, 2, "one mutation registered for event listener change");
+ // first mutation
+ is(mutations[0].target, eventFront1, "first mutation targets eventFront1");
+ is(mutations[0].type, "events", "mutation type is events");
+ is(mutations[0].hasEventListeners, false, "mutation target should have no event listeners");
+ is(eventFront1.hasEventListeners, false, "eventFront2 should have no event listeners");
+ // second mutation
+ is(mutations[1].target, eventFront2, "second mutation targets eventFront2");
+ is(mutations[1].type, "events", "mutation type is events");
+ is(mutations[1].hasEventListeners, false, "mutation target should have no event listeners");
+ is(eventFront2.hasEventListeners, false, "eventFront2 should have no event listeners");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testRemoveMissingEvent() {
+ checkNodesHaveNoEventListener();
+
+ info("try to remove an event listener not previously added");
+ eventNode1.removeEventListener("click", eventListener1);
+
+ info("set any attribute on the node to trigger a mutation")
+ eventNode1.setAttribute("data-attr", "somevalue");
+
+ let mutations = yield waitForMutations();
+ is(mutations.length, 1, "expect only one mutation");
+ isnot(mutations.type, "events", "mutation type should not be events");
+
+ runNextTest();
+ });
+
+ function checkNodesHaveNoEventListener() {
+ is(eventFront1.hasEventListeners, false, "eventFront1 hasEventListeners should be false");
+ is(eventFront2.hasEventListeners, false, "eventFront2 hasEventListeners should be false");
+ };
+
+ function waitForMutations() {
+ return new Promise(resolve => {
+ walker.once("mutations", mutations => {
+ resolve(mutations);
+ });
+ });
+ }
+
+ runNextTest();
+}
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1157469">Mozilla Bug 1157469</a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-mutations-frameload.html b/devtools/server/tests/mochitest/test_inspector-mutations-frameload.html
new file mode 100644
index 000000000..54966cea7
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-mutations-frameload.html
@@ -0,0 +1,214 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gWalker = null;
+var gClient = null;
+var gChildFrame = null;
+var gChildDocument = null;
+var gCleanupConnection = null;
+
+function setup(callback) {
+ let url = document.getElementById("inspectorContent").href;
+ gCleanupConnection = attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ gClient = client;
+ gWalker = walker;
+ }).then(callback));
+ });
+}
+
+function teardown() {
+ gWalker = null;
+ gClient = null;
+ gInspectee = null;
+ gChildFrame = null;
+ if (gCleanupConnection) {
+ gCleanupConnection();
+ gCleanupConnection = null;
+ }
+}
+
+function assertOwnership() {
+ return assertOwnershipTrees(gWalker);
+}
+
+function loadChildSelector(selector) {
+ return gWalker.querySelector(gWalker.rootNode, "#childFrame").then(frame => {
+ ok(frame.numChildren > 0, "Child frame should consider its loaded document as a child.");
+ gChildFrame = frame;
+ return gWalker.children(frame);
+ }).then(children => {
+ return gWalker.querySelectorAll(children.nodes[0], selector);
+ }).then(nodeList => {
+ return nodeList.items();
+ });
+}
+
+function getUnloadedDoc(mutations) {
+ for (let change of mutations) {
+ if (isUnload(change)) {
+ return change.target;
+ }
+ }
+ return null;
+}
+
+addTest(function loadNewChild() {
+ setup(() => {
+ let beforeUnloadSize = 0;
+ // Load a bunch of fronts for actors inside the child frame.
+ promiseDone(loadChildSelector("#longlist div").then(() => {
+ let childFrame = gInspectee.querySelector("#childFrame");
+ childFrame.src = "data:text/html,<html>new child</html>";
+ return waitForMutation(gWalker, isChildList);
+ }).then(mutations => {
+ let unloaded = getUnloadedDoc(mutations);
+ mutations = assertSrcChange(mutations);
+ mutations = assertUnload(mutations);
+ mutations = assertFrameLoad(mutations);
+ mutations = assertChildList(mutations);
+
+ is(mutations.length, 0, "Got the expected mutations.");
+
+ assertOwnership();
+
+ return checkMissing(gClient, unloaded);
+ }).then(() => {
+ teardown();
+ }).then(runNextTest));
+ });
+});
+
+addTest(function loadNewChildTwice() {
+ setup(() => {
+ let beforeUnloadSize = 0;
+ // Load a bunch of fronts for actors inside the child frame.
+ promiseDone(loadChildSelector("#longlist div").then(() => {
+ let childFrame = gInspectee.querySelector("#childFrame");
+ childFrame.src = "data:text/html,<html>new child</html>";
+ return waitForMutation(gWalker, isChildList);
+ }).then(mutations => {
+ // The first load went through as expected (as tested in loadNewChild)
+ // Now change the source again, but this time we *don't* expect
+ // an unload, because we haven't seen the new child document yet.
+ let childFrame = gInspectee.querySelector("#childFrame");
+ childFrame.src = "data:text/html,<html>second new child</html>";
+ return waitForMutation(gWalker, isChildList);
+ }).then(mutations => {
+ mutations = assertSrcChange(mutations);
+ mutations = assertFrameLoad(mutations);
+ mutations = assertChildList(mutations);
+ ok(!getUnloadedDoc(mutations), "Should not have gotten an unload.");
+
+ is(mutations.length, 0, "Got the expected mutations.");
+
+ assertOwnership();
+ }).then(() => {
+ teardown();
+ }).then(runNextTest));
+ });
+});
+
+
+addTest(function loadNewChildTwiceAndCareAboutIt() {
+ setup(() => {
+ let beforeUnloadSize = 0;
+ // Load a bunch of fronts for actors inside the child frame.
+ promiseDone(loadChildSelector("#longlist div").then(() => {
+ let childFrame = gInspectee.querySelector("#childFrame");
+ childFrame.src = "data:text/html,<html>new child</html>";
+ return waitForMutation(gWalker, isChildList);
+ }).then(mutations => {
+ // Read the new child
+ return loadChildSelector("#longlist div");
+ }).then(() => {
+ // Now change the source again, and expect the same results as loadNewChild.
+ let childFrame = gInspectee.querySelector("#childFrame");
+ childFrame.src = "data:text/html,<html>second new child</html>";
+ return waitForMutation(gWalker, isChildList);
+ }).then(mutations => {
+ let unloaded = getUnloadedDoc(mutations);
+
+ mutations = assertSrcChange(mutations);
+ mutations = assertUnload(mutations);
+ mutations = assertFrameLoad(mutations);
+ mutations = assertChildList(mutations);
+
+ is(mutations.length, 0, "Got the expected mutations.");
+
+ assertOwnership();
+
+ return checkMissing(gClient, unloaded);
+ }).then(() => {
+ teardown();
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testBack() {
+ setup(() => {
+ let beforeUnloadSize = 0;
+ // Load a bunch of fronts for actors inside the child frame.
+ promiseDone(loadChildSelector("#longlist div").then(() => {
+ let childFrame = gInspectee.querySelector("#childFrame");
+ childFrame.src = "data:text/html,<html>new child</html>";
+ return waitForMutation(gWalker, isChildList);
+ }).then(mutations => {
+ // Read the new child
+ return loadChildSelector("#longlist div");
+ }).then(() => {
+ // Now use history.back to change the source, and expect the same results as loadNewChild.
+ let childFrame = gInspectee.querySelector("#childFrame");
+ childFrame.contentWindow.history.back();
+ return waitForMutation(gWalker, isChildList);
+ }).then(mutations => {
+ let unloaded = getUnloadedDoc(mutations);
+ mutations = assertSrcChange(mutations);
+ mutations = assertUnload(mutations);
+ mutations = assertFrameLoad(mutations);
+ mutations = assertChildList(mutations);
+ is(mutations.length, 0, "Got the expected mutations.");
+
+ assertOwnership();
+
+ return checkMissing(gClient, unloaded);
+ }).then(() => {
+ teardown();
+ }).then(runNextTest));
+ });
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-mutations-value.html b/devtools/server/tests/mochitest/test_inspector-mutations-value.html
new file mode 100644
index 000000000..352526b13
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-mutations-value.html
@@ -0,0 +1,168 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+const testSummaryLength = 10;
+inspector.setValueSummaryLength(testSummaryLength);
+SimpleTest.registerCleanupFunction(function() {
+ inspector.setValueSummaryLength(inspector.DEFAULT_VALUE_SUMMARY_LENGTH);
+});
+
+var gInspectee = null;
+var gWalker = null;
+var gClient = null;
+var valueNode;
+var valueFront;
+var longStringFront;
+var longString = "stringstringstringstringstringstringstringstringstringstringstring";
+var truncatedLongString = longString.substring(0, testSummaryLength);
+var shortString = "str";
+var shortString2 = "str2";
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(setupValueTest);
+addTest(testKeepLongValue);
+addTest(testSetShortValue);
+addTest(testKeepShortValue);
+addTest(testSetLongValue);
+addTest(setupFrameValueTest);
+addTest(testKeepLongValue);
+addTest(testSetShortValue);
+addTest(testKeepShortValue);
+addTest(testSetLongValue);
+
+function setupValueTest() {
+ valueNode = gInspectee.querySelector("#longstring").firstChild;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#longstring").then(node => {
+ longStringFront = node;
+ return gWalker.children(node);
+ }).then(children => {
+ valueFront = children.nodes[0];
+ }).then(runNextTest));
+}
+
+function setupFrameValueTest() {
+ let frame = gInspectee.querySelector('#childFrame');
+ valueNode = frame.contentDocument.querySelector("#longstring").firstChild;
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => {
+ return gWalker.children(childFrame);
+ }).then(children => {
+ let nodes = children.nodes;
+ is(nodes.length, 1, "There should be only one child of the iframe");
+ is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
+ return gWalker.querySelector(nodes[0], "#longstring");
+ }).then(node => {
+ longStringFront = node;
+ return gWalker.children(node);
+ }).then(children => {
+ valueFront = children.nodes[0];
+ }).then(runNextTest));
+}
+
+function checkNodeFrontValue(front, expectedValue) {
+ return front.getNodeValue().then(longstring => {
+ return longstring.string();
+ }).then(str => {
+ is(str, expectedValue, "Node value is as expected");
+ })
+}
+
+function testKeepLongValue() {
+ // After first setup we should have a long string in the node
+ ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined.");
+
+ valueNode.nodeValue = longString;
+ gWalker.once("mutations", (changes) => {
+ ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined.");
+ ok(!changes.some(change => change.type === "inlineTextChild"),
+ "No inline text child mutation was fired.");
+ checkNodeFrontValue(valueFront, longString).then(runNextTest);
+ });
+}
+
+function testSetShortValue() {
+ ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined.");
+
+ valueNode.nodeValue = shortString;
+ gWalker.once("mutations", (changes) => {
+ ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined.");
+ ok(changes.some(change => change.type === "inlineTextChild"),
+ "An inlineTextChild mutation was fired.");
+ checkNodeFrontValue(valueFront, shortString).then(runNextTest);
+ });
+}
+
+function testKeepShortValue() {
+ ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined.");
+
+ valueNode.nodeValue = shortString2;
+ gWalker.once("mutations", (changes) => {
+ ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined.");
+ ok(!changes.some(change => change.type === "inlineTextChild"),
+ "No inline text child mutation was fired.");
+ checkNodeFrontValue(valueFront, shortString2).then(runNextTest);
+ });
+}
+
+function testSetLongValue() {
+ ok(!!longStringFront.inlineTextChild, "Text node is short enough to be inlined.");
+
+ valueNode.nodeValue = longString;
+ gWalker.once("mutations", (changes) => {
+ ok(!longStringFront.inlineTextChild, "Text node is too long to be inlined.");
+ ok(changes.some(change => change.type === "inlineTextChild"),
+ "An inlineTextChild mutation was fired.");
+ checkNodeFrontValue(valueFront, longString).then(runNextTest);
+ });
+}
+
+addTest(function cleanup() {
+ delete gInspectee;
+ delete gWalker;
+ delete gClient;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-pick-color.html b/devtools/server/tests/mochitest/test_inspector-pick-color.html
new file mode 100644
index 000000000..48ad08468
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-pick-color.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that the inspector actor has the pickColorFromPage and cancelPickColorFromPage
+methods and that when a color is picked the color-picked event is emitted and that when
+the eyedropper is dimissed, the color-pick-canceled event is emitted.
+https://bugzilla.mozilla.org/show_bug.cgi?id=1262439
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1262439</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const Cu = Components.utils;
+ Cu.import("resource://devtools/shared/Loader.jsm");
+ const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+ const {InspectorFront} = devtools.require("devtools/shared/fronts/inspector");
+ const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
+
+ SimpleTest.waitForExplicitFinish();
+
+ let win = null;
+ let inspector = null;
+
+ addAsyncTest(function*() {
+ info("Setting up inspector actor");
+
+ let url = document.getElementById("inspectorContent").href;
+
+ yield new Promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ win = doc.defaultView;
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ runNextTest();
+ });
+
+ addAsyncTest(function*() {
+ info("Start picking a color from the page");
+ yield inspector.pickColorFromPage();
+
+ info("Click in the page and make sure a color-picked event is received");
+ let onColorPicked = waitForEvent("color-picked");
+ win.document.body.click();
+ let color = yield onColorPicked;
+
+ is(color, "#000000", "The color-picked event was received with the right color");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function*() {
+ info("Start picking a color from the page");
+ yield inspector.pickColorFromPage();
+
+ info("Use the escape key to dismiss the eyedropper");
+ let onPickCanceled = waitForEvent("color-pick-canceled");
+
+ let keyboardEvent = win.document.createEvent("KeyboardEvent");
+ keyboardEvent.initKeyEvent("keydown", true, true, win, false, false,
+ false, false, 27, 0);
+ win.document.dispatchEvent(keyboardEvent);
+
+ yield onPickCanceled;
+ ok(true, "The color-pick-canceled event was received");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function*() {
+ info("Start picking a color from the page");
+ yield inspector.pickColorFromPage();
+
+ info("And cancel the color picking");
+ yield inspector.cancelPickColorFromPage();
+
+ runNextTest();
+ });
+
+ function waitForEvent(name) {
+ return new Promise(resolve => inspector.once(name, resolve));
+ }
+
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+<a id="inspectorContent" target="_blank" href="inspector-eyedropper.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-pseudoclass-lock.html b/devtools/server/tests/mochitest/test_inspector-pseudoclass-lock.html
new file mode 100644
index 000000000..64bb03f80
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-pseudoclass-lock.html
@@ -0,0 +1,174 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+const DOMUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"].
+ getService(Components.interfaces.inIDOMUtils);
+
+const KNOWN_PSEUDOCLASSES = [':hover', ':active', ':focus']
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gWalker = null;
+var gClient = null;
+var gCleanupConnection = null;
+
+function setup(callback) {
+ let url = document.getElementById("inspectorContent").href;
+ gCleanupConnection = attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ gClient = client;
+ gWalker = walker;
+ }).then(callback));
+ });
+}
+
+function teardown() {
+ gWalker = null;
+ gClient = null;
+ gInspectee = null;
+ if (gCleanupConnection) {
+ gCleanupConnection();
+ gCleanupConnection = null;
+ }
+}
+
+function checkChange(change, expectation) {
+ is(change.type, "pseudoClassLock", "Expect a pseudoclass lock change.");
+ let target = change.target;
+ if (expectation.id)
+ is(target.id, expectation.id, "Expect a change on node id " + expectation.id);
+ if (expectation.nodeName)
+ is(target.nodeName, expectation.nodeName, "Expect a change on node name " + expectation.nodeName);
+
+ is(target.pseudoClassLocks.length, expectation.pseudos.length,
+ "Expect " + expectation.pseudos.length + " pseudoclass locks.");
+ for (let pseudo of expectation.pseudos) {
+ ok(target.hasPseudoClassLock(pseudo), "Expect lock: " + pseudo);
+ ok(DOMUtils.hasPseudoClassLock(target.rawNode(), pseudo), "Expect lock in dom: " + pseudo);
+ }
+
+ for (let pseudo of KNOWN_PSEUDOCLASSES) {
+ if (!expectation.pseudos.some(expected => pseudo === expected)) {
+ ok(!target.hasPseudoClassLock(pseudo), "Don't expect lock: " + pseudo);
+ ok(!DOMUtils.hasPseudoClassLock(target.rawNode(), pseudo), "Don't expect lock in dom: " + pseudo);
+
+ }
+ }
+}
+
+function checkMutations(mutations, expectations) {
+ is(mutations.length, expectations.length, "Should get the right number of mutations.");
+ for (let i = 0; i < mutations.length; i++) {
+ checkChange(mutations[i] , expectations[i]);
+ }
+}
+
+addTest(function testPseudoClassLock() {
+ let contentNode;
+ let nodeFront;
+ setup(() => {
+ contentNode = gInspectee.querySelector("#b");
+ return promiseDone(gWalker.querySelector(gWalker.rootNode, "#b").then(front => {
+ nodeFront = front;
+ // Lock the pseudoclass alone, no parents.
+ gWalker.addPseudoClassLock(nodeFront, ':active');
+ // Expect a single pseudoClassLock mutation.
+ return promiseOnce(gWalker, "mutations");
+ }).then(mutations => {
+ is(mutations.length, 1, "Should get one mutations");
+ is(mutations[0].target, nodeFront, "Should be the node we tried to apply to");
+ checkChange(mutations[0], {
+ id: "b",
+ nodeName: "DIV",
+ pseudos: [":active"]
+ });
+ }).then(() => {
+ // Now add :hover, this time with parents.
+ gWalker.addPseudoClassLock(nodeFront, ':hover', {parents: true});
+ return promiseOnce(gWalker, "mutations");
+ }).then(mutations => {
+ let expectedMutations = [{
+ id: 'b',
+ nodeName: 'DIV',
+ pseudos: [':hover', ':active'],
+ },
+ {
+ id: 'longlist',
+ nodeName: 'DIV',
+ pseudos: [':hover']
+ },
+ {
+ nodeName: 'BODY',
+ pseudos: [':hover']
+ },
+ {
+ nodeName: 'HTML',
+ pseudos: [':hover']
+ }];
+ checkMutations(mutations, expectedMutations);
+ }).then(() => {
+ // Now remove the :hover on all parents
+ gWalker.removePseudoClassLock(nodeFront, ':hover', {parents: true});
+ return promiseOnce(gWalker, "mutations");
+ }).then(mutations => {
+ let expectedMutations = [{
+ id: 'b',
+ nodeName: 'DIV',
+ // Should still have :active on the original node.
+ pseudos: [':active']
+ },
+ {
+ id: 'longlist',
+ nodeName: 'DIV',
+ pseudos: []
+ },
+ {
+ nodeName: 'BODY',
+ pseudos: []
+ },
+ {
+ nodeName: 'HTML',
+ pseudos: []
+ }];
+ checkMutations(mutations, expectedMutations);
+ }).then(() => {
+ // Now shut down the walker and make sure that clears up the remaining lock.
+ return gWalker.release();
+ }).then(() => {
+ ok(!DOMUtils.hasPseudoClassLock(contentNode, ':active'), "Pseudoclass should have been removed during destruction.");
+ teardown();
+ }).then(runNextTest));
+ });
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-release.html b/devtools/server/tests/mochitest/test_inspector-release.html
new file mode 100644
index 000000000..45412bef0
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-release.html
@@ -0,0 +1,102 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gClient = null;
+
+function assertOwnership() {
+ return assertOwnershipTrees(gWalker);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testReleaseSubtree() {
+ let originalOwnershipSize = 0;
+ let longlist = null;
+ let firstChild = null;
+ promiseDone(gWalker.querySelectorAll(gWalker.rootNode, "#longlist div").then(list => {
+ // Make sure we have the 26 children of longlist in our ownership tree.
+ is(list.length, 26, "Expect 26 div children.");
+ // Make sure we've read in all those children and incorporated them in our ownership tree.
+ return list.items();
+ }).then((items)=> {
+ originalOwnershipSize = assertOwnership();
+
+ // Here is how the ownership tree is summed up:
+ // #document 1
+ // <html> 1
+ // <body> 1
+ // <div id=longlist> 1
+ // <div id=a>a</div> 26*2 (each child plus it's singleTextChild)
+ // ...
+ // <div id=z>z</div>
+ // -----
+ // 56
+ is(originalOwnershipSize, 56, "Correct number of items in ownership tree");
+ firstChild = items[0].actorID;
+ }).then(() => {
+ // Now get the longlist and release it from the ownership tree.
+ return gWalker.querySelector(gWalker.rootNode, "#longlist");
+ }).then(node => {
+ longlist = node.actorID;
+ return gWalker.releaseNode(node);
+ }).then(() => {
+ // Our ownership size should now be 53 fewer (we forgot about #longlist + 26 children + 26 singleTextChild nodes)
+ let newOwnershipSize = assertOwnership();
+ is(newOwnershipSize, originalOwnershipSize - 53,
+ "Ownership tree should be lower");
+ // Now verify that some nodes have gone away
+ return checkMissing(gClient, longlist);
+ }).then(() => {
+ return checkMissing(gClient, firstChild);
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gClient;
+ runNextTest();
+});
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-reload.html b/devtools/server/tests/mochitest/test_inspector-reload.html
new file mode 100644
index 000000000..91252aa8f
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-reload.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gClient = null;
+var gWalker = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ return inspector.getWalker();
+ }).then(walker => {
+ dump(walker.actorID + "\n");
+ ok(walker === gWalker, "getWalker() twice should return the same walker.");
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testReload() {
+ let nodeFront;
+ let oldRootID = gWalker.rootNode.actorID;
+ // Load a node to populate the tree a bit.
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(front => {
+ gInspectee.defaultView.location.reload();
+ return waitForMutation(gWalker, isNewRoot);
+ }).then(() => {
+ ok(gWalker.rootNode.actorID != oldRootID, "Root node should have changed.");
+ }).then(() => {
+ // Make sure we can still access the document
+ return gWalker.querySelector(gWalker.rootNode, "#a");
+ }).then(front => {
+ ok(front.actorID, "Got a new actor ID");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gInspectee;
+ delete gClient;
+ runNextTest();
+});
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-remove.html b/devtools/server/tests/mochitest/test_inspector-remove.html
new file mode 100644
index 000000000..2331c3e30
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-remove.html
@@ -0,0 +1,117 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gClient = null;
+
+function assertOwnership() {
+ return assertOwnershipTrees(gWalker);
+}
+
+function ignoreNode(node) {
+ // Duplicate the walker logic to skip blank nodes...
+ return node.nodeType === Components.interfaces.nsIDOMNode.TEXT_NODE &&
+ !/[^\s]/.test(node.nodeValue);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testRemoveSubtree() {
+ let originalOwnershipSize = 0;
+ let longlist = null;
+ let longlistID = null;
+
+ let nextSibling = gInspectee.querySelector("#longlist").nextSibling;
+ while (nextSibling && ignoreNode(nextSibling)) {
+ nextSibling = nextSibling.nextSibling;
+ }
+
+ let previousSibling = gInspectee.querySelector("#longlist").previousSibling;
+ while (previousSibling && ignoreNode(previousSibling)) {
+ previousSibling = previousSibling.previousSibling;
+ }
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(listFront => {
+ longlist = listFront;
+ longlistID = longlist.actorID;
+ }).then(() => {
+ return gWalker.children(longlist);
+ }).then((items)=> {
+ originalOwnershipSize = assertOwnership();
+ // Here is how the ownership tree is summed up:
+ // #document 1
+ // <html> 1
+ // <body> 1
+ // <div id=longlist> 1
+ // <div id=a>a</div> 26*2 (each child plus it's singleTextChild)
+ // ...
+ // <div id=z>z</div>
+ // -----
+ // 56
+ is(originalOwnershipSize, 56, "Correct number of items in ownership tree");
+ return gWalker.removeNode(longlist);
+ }).then(siblings => {
+ is(siblings.previousSibling.rawNode(), previousSibling, "Should have returned the previous sibling.");
+ is(siblings.nextSibling.rawNode(), nextSibling, "Should have returned the next sibling.");
+ return waitForMutation(gWalker, isChildList);
+ }).then(() => {
+ // Our ownership size should now be 51 fewer (we forgot about #longlist + 26
+ // children + 26 singleTextChild nodes, but learned about #longlist's
+ // prev/next sibling)
+ let newOwnershipSize = assertOwnership();
+ is(newOwnershipSize, originalOwnershipSize - 51,
+ "Ownership tree should be lower");
+ // Now verify that some nodes have gone away
+ return checkMissing(gClient, longlistID);
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gClient;
+ runNextTest();
+});
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-resize.html b/devtools/server/tests/mochitest/test_inspector-resize.html
new file mode 100644
index 000000000..eafa6436c
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-resize.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that the inspector actor emits "resize" events when the page is resized.
+https://bugzilla.mozilla.org/show_bug.cgi?id=1222409
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1222409</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const Cu = Components.utils;
+ const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const promise = require("promise");
+ const {InspectorFront} = require("devtools/shared/fronts/inspector");
+ const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
+
+ SimpleTest.waitForExplicitFinish();
+
+ let win = null;
+ let inspector = null;
+
+ addAsyncTest(function* setup() {
+ info ("Setting up inspector and walker actors.");
+
+ let url = document.getElementById("inspectorContent").href;
+
+ yield new promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ win = doc.defaultView;
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ runNextTest();
+ });
+
+ addAsyncTest(function*() {
+ let walker = yield inspector.getWalker();
+
+ // We can't receive events from the walker if we haven't first executed a
+ // method on the actor to initialize it.
+ yield walker.querySelector(walker.rootNode, "img");
+
+ let {outerWidth, outerHeight} = win;
+ let onResize = new promise(resolve => {
+ walker.once("resize", () => {
+ resolve();
+ });
+ });
+ win.resizeTo(800, 600);
+ yield onResize;
+
+ ok(true, "The resize event was emitted");
+ win.resizeTo(outerWidth, outerHeight);
+
+ runNextTest();
+ });
+
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-resolve-url.html b/devtools/server/tests/mochitest/test_inspector-resolve-url.html
new file mode 100644
index 000000000..1494739ed
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-resolve-url.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=921102
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 921102</title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspector;
+var gDoc;
+
+addTest(function() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gDoc = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ gInspector = InspectorFront(client, tab);
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Resolve a relative URL without providing a context node");
+ gInspector.resolveRelativeURL("test.png?id=4#wow").then(url => {
+ is(url, "chrome://mochitests/content/chrome/devtools/server/tests/" +
+ "mochitest/test.png?id=4#wow");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Resolve an absolute URL without providing a context node");
+ gInspector.resolveRelativeURL("chrome://mochitests/content/chrome/" +
+ "devtools/server/").then(url => {
+ is(url, "chrome://mochitests/content/chrome/devtools/server/");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Resolve a relative URL providing a context node");
+ let node = gDoc.querySelector(".big-horizontal");
+ gInspector.resolveRelativeURL("test.png?id=4#wow", node).then(url => {
+ is(url, "chrome://mochitests/content/chrome/devtools/server/tests/" +
+ "mochitest/test.png?id=4#wow");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Resolve an absolute URL providing a context node");
+ let node = gDoc.querySelector(".big-horizontal");
+ gInspector.resolveRelativeURL("chrome://mochitests/content/chrome/" +
+ "devtools/server/", node).then(url => {
+ is(url, "chrome://mochitests/content/chrome/devtools/server/");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ gInspector = gDoc = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=921102">Mozilla Bug 921102</a>
+<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-retain.html b/devtools/server/tests/mochitest/test_inspector-retain.html
new file mode 100644
index 000000000..e8342cf67
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-retain.html
@@ -0,0 +1,180 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gClient = null;
+var gInspectee = null;
+
+function assertOwnership() {
+ return assertOwnershipTrees(gWalker);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+// Retain a node, and a second-order child (in another document, for kicks)
+// Release the parent of the top item, which should cause one retained orphan.
+
+// Then unretain the top node, which should retain the orphan.
+
+// Then change the source of the iframe, which should kill that orphan.
+
+addTest(function testRetain() {
+ let originalOwnershipSize = 0;
+ let bodyFront = null;
+ let frameFront = null;
+ let childListFront = null;
+ // Get the toplevel body element and retain it.
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "body").then(front => {
+ bodyFront = front;
+ return gWalker.retainNode(bodyFront);
+ }).then(() => {
+ // Get an element in the child frame and retain it.
+ return gWalker.querySelector(gWalker.rootNode, "#childFrame");
+ }).then(frame => {
+ frameFront = frame;
+ return gWalker.children(frame, { maxNodes: 1 }).then(children => {
+ return children.nodes[0];
+ });
+ }).then(childDoc => {
+ return gWalker.querySelector(childDoc, "#longlist");
+ }).then(list => {
+ childListFront = list;
+ originalOwnershipSize = assertOwnership();
+ // and rtain it.
+ return gWalker.retainNode(childListFront);
+ }).then(() => {
+ // OK, try releasing the parent of the first retained.
+ return gWalker.releaseNode(bodyFront.parentNode());
+ }).then(() => {
+ let size = assertOwnership();
+ let clientTree = clientOwnershipTree(gWalker);
+
+ // That request should have freed the parent of the first retained
+ // but moved the rest into the retained orphaned tree.
+ is(ownershipTreeSize(clientTree.root) + ownershipTreeSize(clientTree.retained[0]) + 1,
+ originalOwnershipSize,
+ "Should have only lost one item overall.");
+ is(gWalker._retainedOrphans.size, 1, "Should have retained one orphan");
+ ok(gWalker._retainedOrphans.has(bodyFront), "Should have retained the expected node.");
+ }).then(() => {
+ // Unretain the body, which should promote the childListFront to a retained orphan.
+ return gWalker.unretainNode(bodyFront);
+ }).then(() => {
+ assertOwnership();
+ let clientTree = clientOwnershipTree(gWalker);
+
+ is(gWalker._retainedOrphans.size, 1, "Should still only have one retained orphan.");
+ ok(!gWalker._retainedOrphans.has(bodyFront), "Should have dropped the body node.")
+ ok(gWalker._retainedOrphans.has(childListFront), "Should have retained the child node.")
+ }).then(() => {
+ // Change the source of the iframe, which should kill the retained orphan.
+ gInspectee.querySelector("#childFrame").src = "data:text/html,<html>new child</html>";
+ return waitForMutation(gWalker, isUnretained);
+ }).then(mutations => {
+ assertOwnership();
+ let clientTree = clientOwnershipTree(gWalker);
+ is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
+
+ }).then(runNextTest));
+});
+
+// Get a hold of a node, remove it from the doc and retain it at the same time.
+// We should always win that race (even though the mutation happens before the
+// retain request), because we haven't issued `getMutations` yet.
+addTest(function testWinRace() {
+ let front = null;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(node => {
+ front = node;
+ let contentNode = gInspectee.querySelector("#a");
+ contentNode.parentNode.removeChild(contentNode);
+ // Now wait for that mutation and retain response to come in.
+ return promise.all([
+ gWalker.retainNode(front),
+ waitForMutation(gWalker, isChildList)
+ ]);
+ }).then(() => {
+ assertOwnership();
+ let clientTree = clientOwnershipTree(gWalker);
+ is(gWalker._retainedOrphans.size, 1, "Should have a retained orphan.");
+ ok(gWalker._retainedOrphans.has(front), "Should have retained our expected node.");
+ return gWalker.unretainNode(front);
+ }).then(() => {
+ // Make sure we're clear for the next test.
+ assertOwnership();
+ let clientTree = clientOwnershipTree(gWalker);
+ is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
+ }).then(runNextTest));
+});
+
+// Same as above, but issue the request right after the 'new-mutations' event, so that
+// we *lose* the race.
+addTest(function testLoseRace() {
+ let front = null;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#z").then(node => {
+ front = node;
+ gInspectee.querySelector("#z").parentNode = null;
+ let contentNode = gInspectee.querySelector("#a");
+ contentNode.parentNode.removeChild(contentNode);
+ return promiseOnce(gWalker, "new-mutations");
+ }).then(() => {
+ // Verify that we have an outstanding request (no good way to tell that it's a
+ // getMutations request, but there's nothing else it would be).
+ is(gWalker._requests.length, 1, "Should have an outstanding request.");
+ return gWalker.retainNode(front)
+ }).then(() => { ok(false, "Request should not have succeeded!"); },
+ (err) => {
+ ok(err, "noSuchActor", "Should have lost the race.");
+ let clientTree = clientOwnershipTree(gWalker);
+ is(gWalker._retainedOrphans.size, 0, "Should have no more retained orphans.");
+ // Don't re-throw the error.
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gClient;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-scroll-into-view.html b/devtools/server/tests/mochitest/test_inspector-scroll-into-view.html
new file mode 100644
index 000000000..1e164e83d
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-scroll-into-view.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=901250
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 901250</title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gClient = null;
+var gWalker = null;
+
+function assertOwnership() {
+ assertOwnershipTrees(gWalker);
+}
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(Task.async(function* testScrollIntoView() {
+ let id = "#scroll-into-view";
+ let rect = gInspectee.querySelector(id).getBoundingClientRect();
+ let nodeFront = yield gWalker.querySelector(gWalker.rootNode, id);
+ let inViewport = rect.x >= 0 &&
+ rect.y >= 0 &&
+ rect.y <= gInspectee.defaultView.innerHeight &&
+ rect.x <= gInspectee.defaultView.innerWidth;
+
+ ok(!inViewport, "Element is not in viewport.");
+
+ yield nodeFront.scrollIntoView();
+
+ SimpleTest.executeSoon(() => {
+ rect = gInspectee.querySelector(id).getBoundingClientRect();
+ inViewport = rect.x >= 0 &&
+ rect.y >= 0 &&
+ rect.y <= gInspectee.defaultView.innerHeight &&
+ rect.x <= gInspectee.defaultView.innerWidth;
+
+ ok(inViewport, "Element is in viewport.");
+
+ runNextTest();
+ });
+}));
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gInspectee;
+ delete gClient;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=901250">Mozilla Bug 901250</a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-search-front.html b/devtools/server/tests/mochitest/test_inspector-search-front.html
new file mode 100644
index 000000000..e0f8f77e8
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-search-front.html
@@ -0,0 +1,217 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=835896
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 835896</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const Cu = Components.utils;
+ const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const promise = require("promise");
+ const {InspectorFront} = require("devtools/shared/fronts/inspector");
+ const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
+
+ SimpleTest.waitForExplicitFinish();
+
+ let walkerFront = null;
+ let inspectee = null;
+ let inspector = null;
+
+ // WalkerFront specific tests. These aren't to excercise search
+ // edge cases so much as to test the state the Front maintains between
+ // searches.
+ // See also test_inspector-search.html
+
+ addAsyncTest(function* setup() {
+ info ("Setting up inspector and walker actors.");
+
+ let url = document.getElementById("inspectorContent").href;
+
+ yield new promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ inspectee = doc;
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ walkerFront = yield inspector.getWalker();
+ ok(walkerFront, "getWalker() should return an actor.");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testWalkerFrontDefaults() {
+ info ("Testing search API using WalkerFront.");
+ let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ let fronts = yield nodes.items();
+
+ let frontResult = yield walkerFront.search("");
+ ok(!frontResult, "Null result on front when searching for ''");
+
+ let results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Default options work");
+
+ results = yield walkerFront.search("h2", { });
+ isDeeply(results, {
+ node: fronts[1],
+ type: "search",
+ resultsIndex: 1,
+ resultsLength: 3
+ }, "Search works with empty options");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+ runNextTest();
+ });
+
+ addAsyncTest(function* testMultipleSearches() {
+ info ("Testing search API using WalkerFront (reverse=false)");
+ let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ let fronts = yield nodes.items();
+
+ let results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[1],
+ type: "search",
+ resultsIndex: 1,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[2],
+ type: "search",
+ resultsIndex: 2,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ results = yield walkerFront.search("h2");
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+ runNextTest();
+ });
+
+ addAsyncTest(function* testMultipleSearchesReverse() {
+ info ("Testing search API using WalkerFront (reverse=true)");
+ let nodes = yield walkerFront.querySelectorAll(walkerFront.rootNode, "h2");
+ let fronts = yield nodes.items();
+
+ let results = yield walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[2],
+ type: "search",
+ resultsIndex: 2,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=true)");
+
+ results = yield walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[1],
+ type: "search",
+ resultsIndex: 1,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=true)");
+
+ results = yield walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=true)");
+
+ results = yield walkerFront.search("h2", {reverse: true});
+ isDeeply(results, {
+ node: fronts[2],
+ type: "search",
+ resultsIndex: 2,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=true)");
+
+ results = yield walkerFront.search("h2", {reverse: false});
+ isDeeply(results, {
+ node: fronts[0],
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Search works with multiple results (reverse=false)");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+ runNextTest();
+ });
+
+
+ addAsyncTest(function* testBackwardsCompat() {
+ info ("Simulating a server that doesn't have the new search functionality.");
+ walkerFront.traits.textSearch = false;
+ let front = yield walkerFront.querySelector(walkerFront.rootNode, "h1");
+
+ let results = yield walkerFront.search("h1");
+ isDeeply(results, {
+ node: front,
+ type: "selector",
+ resultsIndex: 0,
+ resultsLength: 1
+ }, "Only querySelectorAll results being returned");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+
+ // Reset the normal textSearch behavior
+ walkerFront.traits.textSearch = true;
+
+ results = yield walkerFront.search("h1");
+ isDeeply(results, {
+ node: front,
+ type: "search",
+ resultsIndex: 0,
+ resultsLength: 3
+ }, "Other results being included");
+
+ // Clear search data to remove result state on the front
+ yield walkerFront.search("");
+ runNextTest();
+ });
+
+ runNextTest();
+};
+ </script>
+</head>
+<body>
+<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-search.html b/devtools/server/tests/mochitest/test_inspector-search.html
new file mode 100644
index 000000000..623d3018d
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-search.html
@@ -0,0 +1,296 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=835896
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 835896</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const Cu = Components.utils;
+ const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const promise = require("promise");
+ const {InspectorFront} = require("devtools/shared/fronts/inspector");
+ const {WalkerSearch, WalkerIndex} =
+ require("devtools/server/actors/utils/walker-search");
+ const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
+
+ SimpleTest.waitForExplicitFinish();
+
+ let walkerActor = null;
+ let walkerSearch = null;
+ let inspectee = null;
+ let inspector = null;
+
+ // WalkerSearch specific tests. This is to make sure search results are
+ // coming back as expected.
+ // See also test_inspector-search-front.html.
+
+ addAsyncTest(function* setup() {
+ info ("Setting up inspector and walker actors.");
+
+ let url = document.getElementById("inspectorContent").href;
+
+ yield new promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ inspectee = doc;
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ let walkerFront = yield inspector.getWalker();
+ ok(walkerFront, "getWalker() should return an actor.");
+
+ walkerActor = DebuggerServer._searchAllConnectionsForActor(walkerFront.actorID);
+ ok(walkerActor,
+ "Got a reference to the walker actor (" + walkerFront.actorID + ")");
+
+ walkerSearch = walkerActor.walkerSearch;
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testIndexExists() {
+ info ("Testing basic index APIs exist.");
+
+ let index = new WalkerIndex(walkerActor);
+ ok(index.data.size > 0, "public index is filled after getting");
+
+ index.clearIndex();
+ ok(!index._data, "private index is empty after clearing");
+ ok(index.data.size > 0, "public index is filled after getting");
+
+ index.destroy();
+ runNextTest();
+ });
+
+ addAsyncTest(function* testSearchExists() {
+ info ("Testing basic search APIs exist.");
+
+ ok(walkerSearch, "walker search exists on the WalkerActor");
+ ok(walkerSearch.search, "walker search has `search` method");
+ ok(walkerSearch.index, "walker search has `index` property");
+ is(walkerSearch.walker, walkerActor, "referencing the correct WalkerActor");
+
+ let search = new WalkerSearch(walkerActor);
+ ok(search, "a new search instance can be created");
+ ok(search.search, "new search instance has `search` method");
+ ok(search.index, "new search instance has `index` property");
+ isnot(search, walkerSearch, "new search instance differs from the WalkerActor's");
+
+ search.destroy();
+ runNextTest();
+ });
+
+ addAsyncTest(function* testEmptySearch() {
+ info ("Testing search with an empty query.");
+ results = walkerSearch.search("");
+ is(results.length, 0, "No results when searching for ''");
+
+ results = walkerSearch.search(null);
+ is(results.length, 0, "No results when searching for null");
+
+ results = walkerSearch.search(undefined);
+ is(results.length, 0, "No results when searching for undefined");
+
+ results = walkerSearch.search(10);
+ is(results.length, 0, "No results when searching for 10");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testBasicSearchData() {
+ let testData = [
+ {
+ desc: "Search for tag with one result.",
+ search: "body",
+ expected: [
+ {node: inspectee.body, type: "tag"}
+ ]
+ },
+ {
+ desc: "Search for tag with multiple results",
+ search: "h2",
+ expected: [
+ {node: inspectee.querySelectorAll("h2")[0], type: "tag"},
+ {node: inspectee.querySelectorAll("h2")[1], type: "tag"},
+ {node: inspectee.querySelectorAll("h2")[2], type: "tag"},
+ ]
+ },
+ {
+ desc: "Search for selector with multiple results",
+ search: "body > h2",
+ expected: [
+ {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
+ ]
+ },
+ {
+ desc: "Search for selector with multiple results",
+ search: ":root h2",
+ expected: [
+ {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
+ ]
+ },
+ {
+ desc: "Search for selector with multiple results",
+ search: "* h2",
+ expected: [
+ {node: inspectee.querySelectorAll("h2")[0], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[1], type: "selector"},
+ {node: inspectee.querySelectorAll("h2")[2], type: "selector"},
+ ]
+ },
+ {
+ desc: "Search with multiple matches in a single tag expecting a single result",
+ search: "💩",
+ expected: [
+ {node: inspectee.getElementById("💩"), type: "attributeValue"}
+ ]
+ },
+ {
+ desc: "Search that has tag and text results",
+ search: "h1",
+ expected: [
+ {node: inspectee.querySelector("h1"), type: "tag"},
+ {node: inspectee.querySelector("h1 + p").childNodes[0], type: "text"},
+ {node: inspectee.querySelector("h1 + p > strong").childNodes[0], type: "text"},
+ ]
+ },
+ ]
+
+ for (let {desc, search, expected} of testData) {
+ info("Running test: " + desc);
+ let results = walkerSearch.search(search);
+ isDeeply(results, expected,
+ "Search returns correct results with '" + search + "'");
+ }
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testPseudoElements() {
+ info ("Testing ::before and ::after element matching");
+
+ let beforeElt = new _documentWalker(inspectee.querySelector("#pseudo"),
+ inspectee.defaultView).firstChild();
+ let afterElt = new _documentWalker(inspectee.querySelector("#pseudo"),
+ inspectee.defaultView).lastChild();
+ let styleText = inspectee.querySelector("style").childNodes[0];
+
+ // ::before
+ let results = walkerSearch.search("::before");
+ isDeeply(results, [ {node: beforeElt, type: "tag"} ],
+ "Tag search works for pseudo element");
+
+ results = walkerSearch.search("_moz_generated_content_before");
+ is(results.length, 0, "No results for anon tag name");
+
+ results = walkerSearch.search("before element");
+ isDeeply(results, [
+ {node: styleText, type: "text"},
+ {node: beforeElt, type: "text"}
+ ], "Text search works for pseudo element");
+
+ // ::after
+ results = walkerSearch.search("::after");
+ isDeeply(results, [ {node: afterElt, type: "tag"} ],
+ "Tag search works for pseudo element");
+
+ results = walkerSearch.search("_moz_generated_content_after");
+ is(results.length, 0, "No results for anon tag name");
+
+ results = walkerSearch.search("after element");
+ isDeeply(results, [
+ {node: styleText, type: "text"},
+ {node: afterElt, type: "text"}
+ ], "Text search works for pseudo element");
+
+ runNextTest();
+ });
+
+ addAsyncTest(function* testSearchMutationChangeResults() {
+ info ("Testing search before and after a mutation.");
+ let expected = [
+ {node: inspectee.querySelectorAll("h3")[0], type: "tag"},
+ {node: inspectee.querySelectorAll("h3")[1], type: "tag"},
+ {node: inspectee.querySelectorAll("h3")[2], type: "tag"},
+ ];
+
+ let results = walkerSearch.search("h3");
+ isDeeply(results, expected, "Search works with tag results");
+
+ yield mutateDocumentAndWaitForMutation(() => {
+ expected[0].node.remove();
+ });
+
+ results = walkerSearch.search("h3");
+ isDeeply(results, [
+ expected[1],
+ expected[2]
+ ], "Results are updated after removal");
+
+ yield new promise(resolve => {
+ info("Waiting for a mutation to happen");
+ let observer = new inspectee.defaultView.MutationObserver(() => {
+ resolve();
+ });
+ observer.observe(inspectee, {attributes: true, subtree: true});
+ inspectee.body.setAttribute("h3", "true");
+ });
+
+ results = walkerSearch.search("h3");
+ isDeeply(results, [
+ {node: inspectee.body, type: "attributeName"},
+ expected[1],
+ expected[2]
+ ], "Results are updated after addition");
+
+ yield new promise(resolve => {
+ info("Waiting for a mutation to happen");
+ let observer = new inspectee.defaultView.MutationObserver(() => {
+ resolve();
+ });
+ observer.observe(inspectee, {attributes: true, childList: true, subtree: true});
+ inspectee.body.removeAttribute("h3");
+ expected[1].node.remove();
+ expected[2].node.remove();
+ });
+
+ results = walkerSearch.search("h3");
+ is(results.length, 0, "Results are updated after removal");
+
+ runNextTest();
+ });
+
+ runNextTest();
+
+ function mutateDocumentAndWaitForMutation(mutationFn) {
+ return new promise(resolve => {
+ info("Listening to markup mutation on the inspectee");
+ let observer = new inspectee.defaultView.MutationObserver(resolve);
+ observer.observe(inspectee, {childList: true, subtree: true});
+ mutationFn();
+ });
+ }
+};
+ </script>
+</head>
+<body>
+<a id="inspectorContent" target="_blank" href="inspector-search-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector-traversal.html b/devtools/server/tests/mochitest/test_inspector-traversal.html
new file mode 100644
index 000000000..ffac8e915
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-traversal.html
@@ -0,0 +1,354 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspectee = null;
+var gClient = null;
+var gWalker = null;
+var checkActorIDs = [];
+
+function assertOwnership() {
+ assertOwnershipTrees(gWalker);
+}
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testWalkerRoot() {
+ // Make sure that refetching the root document of the walker returns the same
+ // actor as the getWalker returned.
+ promiseDone(gWalker.document().then(root => {
+ ok(root === gWalker.rootNode, "Re-fetching the document node should match the root document node.");
+ checkActorIDs.push(root.actorID);
+ assertOwnership();
+ }).then(runNextTest));
+});
+
+addTest(function testInnerHTML() {
+ promiseDone(gWalker.documentElement().then(docElement => {
+ return gWalker.innerHTML(docElement);
+ }).then(longstring => {
+ return longstring.string();
+ }).then(innerHTML => {
+ ok(innerHTML === gInspectee.documentElement.innerHTML, "innerHTML should match");
+ }).then(runNextTest));
+});
+
+addTest(function testOuterHTML() {
+ promiseDone(gWalker.documentElement().then(docElement => {
+ return gWalker.outerHTML(docElement);
+ }).then(longstring => {
+ return longstring.string();
+ }).then(outerHTML => {
+ ok(outerHTML === gInspectee.documentElement.outerHTML, "outerHTML should match");
+ }).then(runNextTest));
+});
+
+addTest(function testSetOuterHTMLNode() {
+ let newHTML = "<p id=\"edit-html-done\">after edit</p>";
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#edit-html").then(node => {
+ return gWalker.setOuterHTML(node, newHTML);
+ }).then(() => {
+ return gWalker.querySelector(gWalker.rootNode, "#edit-html-done");
+ }).then(node => {
+ return gWalker.outerHTML(node);
+ }).then(longstring => {
+ return longstring.string();
+ }).then(outerHTML => {
+ is(outerHTML, newHTML, "outerHTML has been updated");
+ }).then(() => {
+ return gWalker.querySelector(gWalker.rootNode, "#edit-html");
+ }).then(node => {
+ ok(!node, "The node with the old ID cannot be selected anymore");
+ }).then(runNextTest));
+});
+
+addTest(function testQuerySelector() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(node => {
+ is(node.getAttribute("data-test"), "exists", "should have found the right node");
+ assertOwnership();
+ }).then(() => {
+ return gWalker.querySelector(gWalker.rootNode, "unknownqueryselector").then(node => {
+ ok(!node, "Should not find a node here.");
+ assertOwnership();
+ });
+ }).then(runNextTest));
+});
+
+addTest(function testQuerySelectors() {
+ let nodeList = null;
+ let firstNode = null;
+ let nodeListID = null;
+ promiseDone(gWalker.querySelectorAll(gWalker.rootNode, "#longlist div").then(list => {
+ nodeList = list;
+ is(nodeList.length, 26, "Expect 26 div children.");
+ assertOwnership();
+ return nodeList.item(0);
+ }).then(node => {
+ firstNode = node;
+ checkActorIDs.push(node.actorID);
+ is(node.id, "a", "First child should be a");
+ assertOwnership();
+ return nodeList.items();
+ }).then(nodes => {
+ is(nodes.length, 26, "Expect 26 nodes");
+ is(nodes[0], firstNode, "First node should be reused.");
+ ok(nodes[0]._parent, "Parent node should be set.");
+ ok(nodes[0]._next || nodes[0]._prev, "Siblings should be set.");
+ ok(nodes[25]._next || nodes[25]._prev, "Siblings of " + nodes[25] + " should be set.");
+ assertOwnership();
+ return nodeList.items(-1);
+ }).then(nodes => {
+ is(nodes.length, 1, "Expect 1 node")
+ is(nodes[0].id, "z", "Expect it to be the last node.");
+ checkActorIDs.push(nodes[0].actorID);
+ // Save the node list ID so we can ensure it was destroyed.
+ nodeListID = nodeList.actorID;
+ assertOwnership();
+ return nodeList.release();
+ }).then(() => {
+ ok(!nodeList.actorID, "Actor should have been destroyed.");
+ assertOwnership();
+ return checkMissing(gClient, nodeListID);
+ }).then(runNextTest));
+});
+
+// Helper to check the response of requests that return hasFirst/hasLast/nodes
+// node lists (like `children` and `siblings`)
+function nodeArrayChecker(first, last, ids) {
+ return function(response) {
+ is(response.hasFirst, first, "Should " + (first ? "" : "not ") + " have the first node.");
+ is(response.hasLast, last, "Should " + (last ? "" : "not ") + " have the last node.");
+ is(response.nodes.length, ids.length, "Should have " + ids.length + " children listed.");
+ let responseIds = '';
+ for (node of response.nodes) {
+ responseIds += node.id;
+ }
+ is(responseIds, ids, "Correct nodes were returned.");
+ assertOwnership();
+ }
+}
+
+addTest(function testNoChildren() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#empty").then(empty => {
+ assertOwnership();
+ return gWalker.children(empty).then(nodeArrayChecker(true, true, ""));
+ }).then(runNextTest));
+});
+
+addTest(function testLongListTraversal() {
+ var longList;
+ var allChildren;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#longlist").then(node => {
+ longList = node;
+ // First call with no options, expect all children.
+ assertOwnership();
+ return gWalker.children(longList).then(response => {
+ nodeArrayChecker(true, true, "abcdefghijklmnopqrstuvwxyz")(response);
+ allChildren = response.nodes;
+ assertOwnership();
+ });
+ }).then(() => {
+ // maxNodes should limit us to the first 5 nodes.
+ assertOwnership();
+ return gWalker.children(longList, { maxNodes: 5 }).then(nodeArrayChecker(true, false, 'abcde'));
+ }).then(() => {
+ assertOwnership();
+ // maxNodes with the second item centered should still give us the first 5 nodes.
+ return gWalker.children(longList, { maxNodes: 5, center: allChildren[1] }).then(
+ nodeArrayChecker(true, false, 'abcde')
+ );
+ }).then(() => {
+ // maxNodes with a center in the middle of the list should put that item in the middle
+ let center = allChildren[13];
+ is(center.id, 'n', "Make sure I know how to count letters.");
+ return gWalker.children(longList, { maxNodes: 5, center: center }).then(
+ nodeArrayChecker(false, false, 'lmnop')
+ );
+ }).then(() => {
+ // maxNodes with the second-to-last item centered should give us the last 5 nodes.
+ return gWalker.children(longList, { maxNodes: 5, center: allChildren[24] }).then(
+ nodeArrayChecker(false, true, 'vwxyz')
+ );
+ }).then(() => {
+ // maxNodes with a start in the middle should start at that node and fetch 5
+ let start = allChildren[13];
+ is(start.id, 'n', "Make sure I know how to count letters.")
+ return gWalker.children(longList, { maxNodes: 5, start: start }).then(
+ nodeArrayChecker(false, false, 'nopqr')
+ );
+ }).then(() => {
+ // maxNodes near the end should only return what's left
+ return gWalker.children(longList, { maxNodes: 5, start: allChildren[24] }).then(
+ nodeArrayChecker(false, true, 'yz')
+ );
+ }).then(runNextTest));
+});
+
+addTest(function testObjectNodeChildren() {
+ promiseDone(
+ gWalker.querySelector(gWalker.rootNode, "object")
+ .then(object => gWalker.children(object))
+ .then(nodeArrayChecker(true, true, "1"))
+ .then(runNextTest));
+});
+
+addTest(function testSiblings() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#a").then(a => {
+ return gWalker.siblings(a, { maxNodes: 5, center: a }).then(nodeArrayChecker(true, false, "abcde"));
+ }).then(() => {
+ return gWalker.siblings(gWalker.rootNode).then(response => {
+ ok(response.hasFirst && response.hasLast, "Has first and last.");
+ is(response.nodes.length, 1, "Has only the document element.");
+ ok(response.nodes[0] === gWalker.rootNode, "Document element is its own sibling.");
+ });
+ }).then(runNextTest));
+});
+
+addTest(function testNextSibling() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#y").then(y => {
+ is(y.id, "y", "Got the right node.");
+ return gWalker.nextSibling(y);
+ }).then(z => {
+ is(z.id, "z", "nextSibling got the next node.");
+ return gWalker.nextSibling(z);
+ }).then(nothing => {
+ is(nothing, null, "nextSibling on the last node returned null.");
+ }).then(runNextTest));
+});
+
+addTest(function testPreviousSibling() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#b").then(b => {
+ is(b.id, "b", "Got the right node.");
+ return gWalker.previousSibling(b);
+ }).then(a => {
+ is(a.id, "a", "nextSibling got the next node.");
+ return gWalker.previousSibling(a);
+ }).then(nothing => {
+ is(nothing, null, "previousSibling on the first node returned null.");
+ }).then(runNextTest));
+});
+
+
+addTest(function testFrameTraversal() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#childFrame").then(childFrame => {
+ return gWalker.children(childFrame);
+ }).then(children => {
+ let nodes = children.nodes;
+ is(nodes.length, 1, "There should be only one child of the iframe");
+ is(nodes[0].nodeType, Node.DOCUMENT_NODE, "iframe child should be a document node");
+ return gWalker.querySelector(nodes[0], "#z");
+ }).then(childDocumentZ => {
+ return gWalker.parents(childDocumentZ);
+ }).then(parents => {
+ // Expected set of parent tag names for this item:
+ let expectedParents = ['DIV', 'BODY', 'HTML', '#document', 'IFRAME', 'BODY', 'HTML', '#document'];
+ for (let parent of parents) {
+ let expected = expectedParents.shift();
+ is(parent.nodeName, expected, "Got expected parent");
+ }
+ }).then(runNextTest));
+});
+
+addTest(function testLongValue() {
+ const testSummaryLength = 10;
+ inspector.setValueSummaryLength(testSummaryLength);
+ SimpleTest.registerCleanupFunction(function() {
+ inspector.setValueSummaryLength(inspector.DEFAULT_VALUE_SUMMARY_LENGTH);
+ });
+
+ let longstringText = gInspectee.getElementById("longstring").firstChild.nodeValue;
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#longstring").then(node => {
+ ok(!node.inlineTextChild, "Text is too long to be inlined");
+ // Now we need to get the text node child...
+ return gWalker.children(node, { maxNodes: 1 });
+ }).then(children => {
+ let textNode = children.nodes[0];
+ is(textNode.nodeType, Node.TEXT_NODE, "Value should be a text node");
+ return textNode;
+ }).then(textNode => {
+ return textNode.getNodeValue();
+ }).then(value => {
+ return value.string();
+ }).then(valueStr => {
+ is(valueStr, longstringText, "Full node value should match the string from the document.");
+ }).then(runNextTest));
+});
+
+addTest(function testShortValue() {
+ let shortstringText = gInspectee.getElementById("shortstring").firstChild.nodeValue;
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#shortstring").then(node => {
+ ok(!!node.inlineTextChild, "Text is short enough to be inlined");
+ // Now we need to get the text node child...
+ return gWalker.children(node, { maxNodes: 1 });
+ }).then(children => {
+ let textNode = children.nodes[0];
+ is(textNode.nodeType, Node.TEXT_NODE, "Value should be a text node");
+ return textNode;
+ }).then(textNode => {
+ return textNode.getNodeValue();
+ }).then(value => {
+ return value.string();
+ }).then(valueStr => {
+ is(valueStr, shortstringText, "Full node value should match the string from the document.");
+ }).then(runNextTest));
+});
+
+addTest(function testReleaseWalker() {
+ checkActorIDs.push(gWalker.actorID);
+
+ promiseDone(gWalker.release().then(() => {
+ let promises = Array.from(checkActorIDs, (id) => checkMissing(gClient, id));
+ return promise.all(promises)
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ delete gInspectee;
+ delete gClient;
+ runNextTest();
+});
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-traversal-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector_getImageData-wait-for-load.html b/devtools/server/tests/mochitest/test_inspector_getImageData-wait-for-load.html
new file mode 100644
index 000000000..63eb0bd3c
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector_getImageData-wait-for-load.html
@@ -0,0 +1,136 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for InspectorActor.getImageData() in following cases:
+ * Image takes too long to load (the method rejects after a timeout).
+ * Image is loading when the method is called and the load finishes before
+ timeout.
+ * Image fails to load.
+
+https://bugzilla.mozilla.org/show_bug.cgi?id=1192536
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1192536</title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+
+const flags = require("devtools/shared/flags");
+const wasTesting = flags.testing;
+SimpleTest.registerCleanupFunction(() => flags.testing = wasTesting);
+
+const PATH = "http://mochi.test:8888/chrome/devtools/server/tests/mochitest/";
+const BASE_IMAGE = PATH + "inspector-delay-image-response.sjs";
+const DELAYED_IMAGE = BASE_IMAGE + "?delay=300";
+const TIMEOUT_IMAGE = BASE_IMAGE + "?delay=50000";
+const NONEXISTENT_IMAGE = PATH + "this-does-not-exist.png";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gImg = null;
+var gNodeFront = null;
+var gWalker = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+
+ promiseDone(inspector.getWalker().then(walker => {
+ gWalker = walker;
+ return walker.querySelector(gWalker.rootNode, "img.custom").then(img => {
+ gNodeFront = img;
+ gImg = doc.querySelector("img.custom");
+
+ ok(gNodeFront, "Got the image NodeFront.");
+ ok(gImg, "Got the image Node.");
+ });
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testTimeout() {
+ info("Testing that the method aborts if the image takes too long to load.");
+
+ // imageToImageData() only times out when flags.testing is not set.
+ flags.testing = false;
+
+ gImg.src = TIMEOUT_IMAGE;
+
+ info("Calling getImageData().");
+ ensureRejects(gNodeFront.getImageData(), "Timeout image").then(runNextTest);
+});
+
+addTest(function testNonExistentImage() {
+ info("Testing that non-existent image causes a rejection.");
+
+ // This test shouldn't hit the timeout.
+ flags.testing = true;
+
+ gImg.src = NONEXISTENT_IMAGE;
+
+ info("Calling getImageData().");
+ ensureRejects(gNodeFront.getImageData(), "Non-existent image").then(runNextTest);
+});
+
+addTest(function testDelayedImage() {
+ info("Testing that the method waits for an image to load.");
+
+ // This test shouldn't hit the timeout.
+ flags.testing = true;
+
+ gImg.src = DELAYED_IMAGE;
+
+ info("Calling getImageData().");
+ checkImageData(gNodeFront.getImageData()).then(runNextTest);
+});
+
+addTest(function cleanup() {
+ delete gImg;
+ delete gNodeFront
+ delete gWalker;
+ runNextTest();
+});
+
+/**
+ * Asserts that the given promise rejects.
+ */
+function ensureRejects(promise, desc) {
+ return promise.then(() => {
+ ok(false, desc + ": promise resolved unexpectedly.");
+ }, () => {
+ ok(true, desc + ": promise rejected as expected.");
+ });
+}
+
+/**
+ * Waits for the call to getImageData() the resolve and checks that the image
+ * size is reported correctly.
+ */
+function checkImageData(promise, { width, height } = { width: 1, height: 1 }) {
+ return promise.then(({ size }) => {
+ is(size.naturalWidth, width, "The width is correct.");
+ is(size.naturalHeight, height, "The height is correct.");
+ });
+}
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1192536">Mozilla Bug 1192536</a>
+<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector_getImageData.html b/devtools/server/tests/mochitest/test_inspector_getImageData.html
new file mode 100644
index 000000000..be3c24194
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector_getImageData.html
@@ -0,0 +1,166 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=932937
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 932937</title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+
+ promiseDone(inspector.getWalker().then(walker => {
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testLargeImage() {
+ // Select the image node from the test page
+ gWalker.querySelector(gWalker.rootNode, ".big-horizontal").then(img => {
+ ok(img, "Image node found in the test page");
+ ok(img.getImageData, "Image node has the getImageData function");
+
+ img.getImageData(100).then(imageData => {
+ ok(imageData.data, "Image data actor was sent back");
+ ok(imageData.size, "Image size info was sent back too");
+ is(imageData.size.naturalWidth, 5333, "Natural width of the image correct");
+ is(imageData.size.naturalHeight, 3000, "Natural width of the image correct");
+ ok(imageData.size.resized, "Image was resized");
+
+ imageData.data.string().then(str => {
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+ });
+ });
+ });
+});
+
+addTest(function testLargeCanvas() {
+ // Select the canvas node from the test page
+ gWalker.querySelector(gWalker.rootNode, ".big-vertical").then(canvas => {
+ ok(canvas, "Image node found in the test page");
+ ok(canvas.getImageData, "Image node has the getImageData function");
+
+ canvas.getImageData(350).then(imageData => {
+ ok(imageData.data, "Image data actor was sent back");
+ ok(imageData.size, "Image size info was sent back too");
+ is(imageData.size.naturalWidth, 1000, "Natural width of the image correct");
+ is(imageData.size.naturalHeight, 2000, "Natural width of the image correct");
+ ok(imageData.size.resized, "Image was resized");
+
+ imageData.data.string().then(str => {
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+ });
+ });
+ });
+});
+
+addTest(function testSmallImage() {
+ // Select the small image node from the test page
+ gWalker.querySelector(gWalker.rootNode, ".small").then(img => {
+ ok(img, "Image node found in the test page");
+ ok(img.getImageData, "Image node has the getImageData function");
+
+ img.getImageData().then(imageData => {
+ ok(imageData.data, "Image data actor was sent back");
+ ok(imageData.size, "Image size info was sent back too");
+ is(imageData.size.naturalWidth, 245, "Natural width of the image correct");
+ is(imageData.size.naturalHeight, 240, "Natural width of the image correct");
+ ok(!imageData.size.resized, "Image was NOT resized");
+
+ imageData.data.string().then(str => {
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+ });
+ });
+ });
+});
+
+addTest(function testDataImage() {
+ // Select the data image node from the test page
+ gWalker.querySelector(gWalker.rootNode, ".data").then(img => {
+ ok(img, "Image node found in the test page");
+ ok(img.getImageData, "Image node has the getImageData function");
+
+ img.getImageData(14).then(imageData => {
+ ok(imageData.data, "Image data actor was sent back");
+ ok(imageData.size, "Image size info was sent back too");
+ is(imageData.size.naturalWidth, 28, "Natural width of the image correct");
+ is(imageData.size.naturalHeight, 28, "Natural width of the image correct");
+ ok(imageData.size.resized, "Image was resized");
+
+ imageData.data.string().then(str => {
+ ok(str, "We have an image data string!");
+ testResizing(imageData, str);
+ });
+ });
+ });
+});
+
+addTest(function testNonImgOrCanvasElements() {
+ gWalker.querySelector(gWalker.rootNode, "body").then(body => {
+ ensureRejects(body.getImageData(), "Invalid element").then(runNextTest);
+ });
+});
+
+addTest(function cleanup() {
+ delete gWalker;
+ runNextTest();
+});
+
+/**
+ * Checks if the server told the truth about resizing the image
+ */
+function testResizing(imageData, str) {
+ let img = document.createElement("img");
+ img.addEventListener("load", () => {
+ let resized = !(img.naturalWidth == imageData.size.naturalWidth &&
+ img.naturalHeight == imageData.size.naturalHeight);
+ is(imageData.size.resized, resized, "Server told the truth about resizing");
+ runNextTest();
+ }, false);
+ img.src = str;
+}
+
+/**
+ * Asserts that the given promise rejects.
+ */
+function ensureRejects(promise, desc) {
+ return promise.then(() => {
+ ok(false, desc + ": promise resolved unexpectedly.");
+ }, () => {
+ ok(true, desc + ": promise rejected as expected.");
+ });
+}
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=932937">Mozilla Bug 932937</a>
+<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector_getImageDataFromURL.html b/devtools/server/tests/mochitest/test_inspector_getImageDataFromURL.html
new file mode 100644
index 000000000..473a62275
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector_getImageDataFromURL.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Tests for InspectorActor.getImageDataFromURL() in following cases:
+ * Normal case, image loads after a small delay.
+ * Image takes too long to load (the method rejects after a timeout).
+ * Image fails to load.
+
+https://bugzilla.mozilla.org/show_bug.cgi?id=1192536
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1192536</title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+
+const flags = require("devtools/shared/flags");
+const wasTesting = flags.testing;
+SimpleTest.registerCleanupFunction(() => flags.testing = wasTesting);
+
+const PATH = "http://mochi.test:8888/chrome/devtools/server/tests/mochitest/";
+const BASE_IMAGE = PATH + "inspector-delay-image-response.sjs";
+const DELAYED_IMAGE = BASE_IMAGE + "?delay=300";
+const TIMEOUT_IMAGE = BASE_IMAGE + "?delay=50000";
+const NONEXISTENT_IMAGE = PATH + "this-does-not-exist.png";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gInspector = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ gInspector = InspectorFront(client, tab);
+ runNextTest();
+ });
+});
+
+addTest(function testTimeout() {
+ info("Testing that the method aborts if the image takes too long to load.");
+
+ // imageToImageData() only times out when flags.testing is not set.
+ flags.testing = false;
+
+ ensureRejects(gInspector.getImageDataFromURL(TIMEOUT_IMAGE),
+ "Image that loads for too long").then(runNextTest);
+});
+
+addTest(function testNonExistentImage() {
+ info("Testing that non-existent image causes a rejection.");
+
+ // This test shouldn't hit the timeout.
+ flags.testing = true;
+
+ ensureRejects(gInspector.getImageDataFromURL(NONEXISTENT_IMAGE),
+ "Non-existent image").then(runNextTest);
+});
+
+addTest(function testNormalImage() {
+ info("Testing that the method waits for an image to load.");
+
+ // This test shouldn't hit the timeout.
+ flags.testing = true;
+
+ checkImageData(gInspector.getImageDataFromURL(DELAYED_IMAGE)).then(runNextTest);
+});
+
+addTest(function cleanup() {
+ delete gInspector;
+ runNextTest();
+});
+
+/**
+ * Asserts that the given promise rejects.
+ */
+function ensureRejects(promise, desc) {
+ return promise.then(() => {
+ ok(false, desc + ": promise resolved unexpectedly.");
+ }, () => {
+ ok(true, desc + ": promise rejected as expected.");
+ });
+}
+
+/**
+ * Waits for the call to getImageData() the resolve and checks that the image
+ * size is reported correctly.
+ */
+function checkImageData(promise, { width, height } = { width: 1, height: 1 }) {
+ return promise.then(({ size }) => {
+ is(size.naturalWidth, width, "The width is correct.");
+ is(size.naturalHeight, height, "The height is correct.");
+ });
+}
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1192536">Mozilla Bug 1192536</a>
+<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_inspector_getNodeFromActor.html b/devtools/server/tests/mochitest/test_inspector_getNodeFromActor.html
new file mode 100644
index 000000000..6c06d8a7b
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector_getNodeFromActor.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1155653
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1155653</title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/shared/fronts/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker;
+
+addTest(function() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+
+ promiseDone(inspector.getWalker().then(walker => {
+ gWalker = walker;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function() {
+ info("Try to get a NodeFront from an invalid actorID");
+ gWalker.getNodeFromActor("invalid", ["node"]).then(node => {
+ ok(!node, "The node returned is null");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Try to get a NodeFront from a valid actorID but invalid path");
+ gWalker.getNodeFromActor(gWalker.actorID, ["invalid", "path"]).then(node => {
+ ok(!node, "The node returned is null");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Try to get a NodeFront from a valid actorID and valid path");
+ gWalker.getNodeFromActor(gWalker.actorID, ["rootDoc"]).then(rootDocNode => {
+ ok(rootDocNode, "A node was returned");
+ is(rootDocNode, gWalker.rootNode, "The right node was returned");
+ runNextTest();
+ });
+});
+
+addTest(function() {
+ info("Try to get a NodeFront from a valid actorID and valid complex path");
+ gWalker.getNodeFromActor(gWalker.actorID,
+ ["tabActor", "window", "document", "body"]).then(bodyNode => {
+ ok(bodyNode, "A node was returned");
+ gWalker.querySelector(gWalker.rootNode, "body").then(node => {
+ is(bodyNode, node, "The body node was returned");
+ runNextTest();
+ });
+ });
+});
+
+addTest(function() {
+ gWalker = null;
+ runNextTest();
+});
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1155653">Mozilla Bug 1155653</a>
+<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_makeGlobalObjectReference.html b/devtools/server/tests/mochitest/test_makeGlobalObjectReference.html
new file mode 100644
index 000000000..8bd7e0476
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_makeGlobalObjectReference.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=914405
+
+Debugger.prototype.makeGlobalObjectReference should dereference WindowProxy
+(outer window) objects.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug 914405</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+ SimpleTest.waitForExplicitFinish();
+
+ var iframe = document.createElement("iframe");
+ iframe.src = "data:text/html,<html>The word 'smorgasbord', spoken by an adorably plump child, symbolizing prosperity</html>";
+ iframe.onload = iframeOnLoad;
+ document.body.appendChild(iframe);
+
+ function iframeOnLoad() {
+ var dbg = new Debugger;
+
+ var g1o = iframe.contentWindow; // 'o' for 'outer window'
+ ok(!dbg.hasDebuggee(g1o), "iframe is not initially a debuggee");
+
+ // Like addDebuggee, makeGlobalObjectReference innerizes.
+ // 'i' stands for 'inner window'.
+ // 'DO' stands for 'Debugger.Object'.
+ var g1iDO = dbg.makeGlobalObjectReference(g1o);
+ ok(!dbg.hasDebuggee(g1o), "makeGlobalObjectReference does not add g1 as debuggee, designated via outer");
+ ok(!dbg.hasDebuggee(g1iDO), "makeGlobalObjectReference does not add g1 as debuggee, designated via D.O ");
+
+ // Wrapping an object automatically outerizes it, so dereferencing an
+ // inner object D.O gets you an outer object.
+ // ('===' does distinguish inner and outer objects.)
+ // (That's a capital '=', if you must know.)
+ ok(g1iDO.unsafeDereference() === g1o, "g1iDO has the right referent");
+
+ // However, Debugger.Objects do distinguish inner and outer windows.
+ var g1oDO = g1iDO.makeDebuggeeValue(g1o);
+ ok(g1iDO !== g1oDO, "makeDebuggeeValue doesn't innerize");
+ ok(g1iDO.unsafeDereference() === g1oDO.unsafeDereference(),
+ "unsafeDereference() outerizes, so inner and outer window D.Os both dereference to outer");
+
+ ok(dbg.addDebuggee(g1o) === g1iDO, "addDebuggee returns the inner window's D.O");
+ ok(dbg.hasDebuggee(g1o), "addDebuggee adds the correct global");
+ ok(dbg.hasDebuggee(g1iDO), "hasDebuggee can take a D.O referring to the inner window");
+ ok(dbg.hasDebuggee(g1oDO), "hasDebuggee can take a D.O referring to the outer window");
+
+ var iframe2 = document.createElement("iframe");
+ iframe2.src = "data:text/html,<html>Her retrospection, in hindsight, was prescient.</html>";
+ iframe2.onload = iframe2OnLoad;
+ document.body.appendChild(iframe2);
+
+ function iframe2OnLoad() {
+ // makeGlobalObjectReference dereferences CCWs.
+ var g2o = iframe2.contentWindow;
+ g2o.g1o = g1o;
+
+ var g2iDO = dbg.addDebuggee(g2o);
+ var g2g1oDO = g2iDO.getOwnPropertyDescriptor('g1o').value;
+ ok(g2g1oDO !== g1oDO, "g2's cross-compartment wrapper for g1o gets its own D.O");
+ ok(g2g1oDO.unwrap() === g1oDO,
+ "unwrapping g2's cross-compartment wrapper for g1o gets the right D.O");
+ ok(dbg.makeGlobalObjectReference(g2g1oDO) === g1iDO,
+ "makeGlobalObjectReference unwraps cross-compartment wrappers, and innerizes");
+
+ SimpleTest.finish();
+ }
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory.html b/devtools/server/tests/mochitest/test_memory.html
new file mode 100644
index 000000000..9f191da76
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 923275 - Add a memory monitor widget to the developer toolbar
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ var measurement = yield memory.measure();
+ ok(measurement.total > 0, "total memory is valid");
+ ok(measurement.domSize > 0, "domSize is valid");
+ ok(measurement.styleSize > 0, "styleSize is valid");
+ ok(measurement.jsObjectsSize > 0, "jsObjectsSize is valid");
+ ok(measurement.jsStringsSize > 0, "jsStringsSize is valid");
+ ok(measurement.jsOtherSize > 0, "jsOtherSize is valid");
+ ok(measurement.otherSize > 0, "otherSize is valid");
+ ok(measurement.jsMilliseconds, "jsMilliseconds is valid");
+ ok(measurement.nonJSMilliseconds, "nonJSMilliseconds is valid");
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_01.html b/devtools/server/tests/mochitest/test_memory_allocations_01.html
new file mode 100644
index 000000000..2ed9b74bc
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_allocations_01.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test recording allocations.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ yield memory.startRecordingAllocations();
+ ok(true, "Can start recording allocations");
+
+ // Allocate some objects.
+
+ var alloc1, alloc2, alloc3;
+ (function outer() {
+ (function middle() {
+ (function inner() {
+ alloc1 = {}; alloc1.line = Error().lineNumber;
+ alloc2 = []; alloc2.line = Error().lineNumber;
+ alloc3 = new function() {}; alloc3.line = Error().lineNumber;
+ }());
+ }());
+ }());
+
+ var response = yield memory.getAllocations();
+
+ yield memory.stopRecordingAllocations();
+ ok(true, "Can stop recording allocations");
+
+ // Filter out allocations by library and test code, and get only the
+ // allocations that occurred in our test case above.
+
+ function isTestAllocation(alloc) {
+ var frame = response.frames[alloc];
+ return frame
+ && frame.functionDisplayName === "inner"
+ && (frame.line === alloc1.line
+ || frame.line === alloc2.line
+ || frame.line === alloc3.line);
+ }
+
+ var testAllocations = response.allocations.filter(isTestAllocation);
+ ok(testAllocations.length >= 3,
+ "Should find our 3 test allocations (plus some allocations for the error "
+ + "objects used to get line numbers)");
+
+ // For each of the test case's allocations, ensure that the parent frame
+ // indices are correct. Also test that we did get an allocation at each
+ // line we expected (rather than a bunch on the first line and none on the
+ // others, etc).
+
+ var expectedLines = new Set([alloc1.line, alloc2.line, alloc3.line]);
+
+ for (var alloc of testAllocations) {
+ var innerFrame = response.frames[alloc];
+ ok(innerFrame, "Should get the inner frame");
+ is(innerFrame.functionDisplayName, "inner");
+ expectedLines.delete(innerFrame.line);
+
+ var middleFrame = response.frames[innerFrame.parent];
+ ok(middleFrame, "Should get the middle frame");
+ is(middleFrame.functionDisplayName, "middle");
+
+ var outerFrame = response.frames[middleFrame.parent];
+ ok(outerFrame, "Should get the outer frame");
+ is(outerFrame.functionDisplayName, "outer");
+
+ // Not going to test the rest of the frames because they are Task.jsm
+ // and promise frames and it gets gross. Plus, I wouldn't want this test
+ // to start failing if they changed their implementations in a way that
+ // added or removed stack frames here.
+ }
+
+ is(expectedLines.size, 0,
+ "Should have found all the expected lines");
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_02.html b/devtools/server/tests/mochitest/test_memory_allocations_02.html
new file mode 100644
index 000000000..0133a27b0
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_allocations_02.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1132764 - Test controlling the maximum allocations log length over the RDP.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ var allocs = [];
+ var eventsFired = 0;
+ var intervalId = null;
+ function onAlloc () {
+ eventsFired++;
+ }
+ function startAllocating () {
+ intervalId = setInterval(() => {
+ for (var i = 100000; --i;) {
+ allocs.push(new Object());
+ }
+ }, 1);
+ }
+ function stopAllocating () {
+ clearInterval(intervalId);
+ }
+
+ memory.on("allocations", onAlloc);
+
+ yield memory.startRecordingAllocations({
+ drainAllocationsTimeout: 10
+ });
+
+ yield waitUntil(() => eventsFired > 5);
+ ok(eventsFired > 5, "Some allocation events fired without allocating much via auto drain");
+ yield memory.stopRecordingAllocations();
+
+ // Set a really high auto drain timer so we can test if
+ // it fires on GC
+ eventsFired = 0;
+ var startTime = performance.now();
+ var drainTimer = 1000000;
+ yield memory.startRecordingAllocations({
+ drainAllocationsTimeout: drainTimer
+ });
+
+ startAllocating();
+ yield waitUntil(() => {
+ Cu.forceGC();
+ return eventsFired > 1;
+ });
+ stopAllocating();
+ ok(performance.now() - drainTimer < startTime, "Allocation events fired on GC before timer");
+ yield memory.stopRecordingAllocations();
+
+ memory.off("allocations", onAlloc);
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_03.html b/devtools/server/tests/mochitest/test_memory_allocations_03.html
new file mode 100644
index 000000000..b7d18d7ed
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_allocations_03.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test that frames keep the same index while we are recording.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ yield memory.startRecordingAllocations();
+
+ // Allocate twice with the exact same stack (hence setTimeout rather than
+ // allocating directly in the generator), but with getAllocations() calls in
+ // between.
+
+ var allocs = [];
+ function allocator() {
+ allocs.push({});
+ }
+
+ setTimeout(allocator, 1);
+ yield waitForTime(2);
+ var first = yield memory.getAllocations();
+
+ setTimeout(allocator, 1);
+ yield waitForTime(2);
+ var second = yield memory.getAllocations();
+
+ yield memory.stopRecordingAllocations();
+
+ // Assert that each frame in the first response has the same index in the
+ // second response. This isn't commutative, so we don't check that all
+ // of the second response's frames are the same in the first response,
+ // because there might be new allocations that happen after the first query
+ // but before the second.
+
+ function assertSameFrame(a, b) {
+ info("Checking frames at index " + i + ":");
+ info(" First frame = " + JSON.stringify(a, null, 4));
+ info(" Second frame = " + JSON.stringify(b, null, 4));
+
+ is(!!a, !!b);
+ if (!a || !b) {
+ return;
+ }
+
+ is(a.source, b.source);
+ is(a.line, b.line);
+ is(a.column, b.column);
+ is(a.functionDisplayName, b.functionDisplayName);
+ is(a.parent, b.parent);
+ }
+
+ for (var i = 0; i < first.frames.length; i++) {
+ assertSameFrame(first.frames[i], second.frames[i]);
+ }
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_04.html b/devtools/server/tests/mochitest/test_memory_allocations_04.html
new file mode 100644
index 000000000..5568736d3
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_allocations_04.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1068171 - Test controlling the memory actor's allocation sampling probability.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ var allocs = [];
+ function allocator() {
+ for (var i = 0; i < 100; i++) {
+ allocs.push({});
+ }
+ }
+
+ var testProbability = Task.async(function* (p, expected) {
+ info("probability = " + p);
+ yield memory.startRecordingAllocations({
+ probability: p
+ });
+ allocator();
+ var response = yield memory.getAllocations();
+ yield memory.stopRecordingAllocations();
+ return response.allocations.length;
+ });
+
+ is((yield testProbability(0.0)), 0,
+ "With probability = 0.0, we shouldn't get any allocations.");
+
+ ok((yield testProbability(1.0)) >= 100,
+ "With probability = 1.0, we should get all 100 allocations (plus "
+ + "whatever allocations the actor and SpiderMonkey make).");
+
+ // We don't test any other probabilities because the test would be
+ // non-deterministic. We don't have a way to control the PRNG like we do in
+ // jit-tests
+ // (js/src/jit-test/tests/debug/Memory-allocationsSamplingProbability-*.js).
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_05.html b/devtools/server/tests/mochitest/test_memory_allocations_05.html
new file mode 100644
index 000000000..0eeb7bd16
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_allocations_05.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1068144 - Test getting the timestamps for allocations.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ var allocs = [];
+ function allocator() {
+ allocs.push(new Object);
+ }
+
+ // Using setTimeout results in wildly varying delays that make it hard to
+ // test our timestamps and results in intermittent failures. Instead, we
+ // actually spin an empty loop for a whole millisecond.
+ function actuallyWaitOneWholeMillisecond() {
+ var start = window.performance.now();
+ while (window.performance.now() - start < 1.000) ;
+ }
+
+ yield memory.startRecordingAllocations();
+
+ allocator();
+ actuallyWaitOneWholeMillisecond();
+ allocator();
+ actuallyWaitOneWholeMillisecond();
+ allocator();
+
+ var response = yield memory.getAllocations();
+ yield memory.stopRecordingAllocations();
+
+ ok(response.allocationsTimestamps, "The response should have timestamps.");
+ is(response.allocationsTimestamps.length, response.allocations.length,
+ "There should be a timestamp for every allocation.");
+
+ var allocatorIndices = response.allocations
+ .map(function (a, idx) {
+ var frame = response.frames[a];
+ if (frame && frame.functionDisplayName === "allocator") {
+ return idx;
+ }
+ })
+ .filter(function (idx) {
+ return idx !== undefined;
+ });
+
+ is(allocatorIndices.length, 3, "Should have our 3 allocations from the `allocator` timeouts.");
+
+ var lastTimestamp;
+ for (var i = 0; i < 3; i++) {
+ var timestamp = response.allocationsTimestamps[allocatorIndices[i]];
+ info("timestamp", timestamp);
+ ok(timestamp, "We should have a timestamp for the `allocator` allocation.");
+
+ if (lastTimestamp) {
+ var delta = timestamp - lastTimestamp;
+ info("delta since last timestamp", delta);
+ ok(delta >= 1 /* ms */,
+ "The timestamp should be about 1 ms after the last timestamp.");
+ }
+
+ lastTimestamp = timestamp;
+ }
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_06.html b/devtools/server/tests/mochitest/test_memory_allocations_06.html
new file mode 100644
index 000000000..56a9f8041
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_allocations_06.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1132764 - Test controlling the maximum allocations log length over the RDP.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ var allocs = [];
+ function allocator() {
+ allocs.push(new Object);
+ }
+
+ yield memory.startRecordingAllocations({
+ maxLogLength: 1
+ });
+
+ allocator();
+ allocator();
+ allocator();
+
+ var response = yield memory.getAllocations();
+ yield memory.stopRecordingAllocations();
+
+ is(response.allocations.length, 1,
+ "There should only be one entry in the allocations log.");
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_allocations_07.html b/devtools/server/tests/mochitest/test_memory_allocations_07.html
new file mode 100644
index 000000000..c26c2d8ec
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_allocations_07.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1192335 - Test getting the byte sizes for allocations.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ var allocs = [];
+ function allocator() {
+ allocs.push(new Object);
+ }
+
+ yield memory.startRecordingAllocations();
+
+ allocator();
+ allocator();
+ allocator();
+
+ var response = yield memory.getAllocations();
+ yield memory.stopRecordingAllocations();
+
+ ok(response.allocationSizes, "The response should have bytesizes.");
+ is(response.allocationSizes.length, response.allocations.length,
+ "There should be a bytesize for every allocation.");
+ ok(response.allocationSizes.length >= 3,
+ "There are atleast 3 allocations.");
+ ok(response.allocationSizes.every(isPositiveNumber), "every bytesize is a positive number");
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+
+function isPositiveNumber (n) {
+ return typeof n === "number" && n > 0;
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_attach_01.html b/devtools/server/tests/mochitest/test_memory_attach_01.html
new file mode 100644
index 000000000..5b0b3f75e
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_attach_01.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 960671 - Test attaching and detaching from a memory actor.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+ ok(true, "Shouldn't have gotten an error attaching.");
+ yield memory.detach();
+ ok(true, "Shouldn't have gotten an error detaching.");
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_attach_02.html b/devtools/server/tests/mochitest/test_memory_attach_02.html
new file mode 100644
index 000000000..76269a6df
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_attach_02.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 960671 - Test attaching and detaching while in the wrong state.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+
+ var e = null;
+ try {
+ yield memory.detach();
+ }
+ catch (ee) {
+ e = ee;
+ }
+ ok(e, "Should have hit the wrongState error");
+
+ yield memory.attach();
+
+ e = null;
+ try {
+ yield memory.attach();
+ }
+ catch (ee) {
+ e = ee;
+ }
+ ok(e, "Should have hit the wrongState error");
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_census.html b/devtools/server/tests/mochitest/test_memory_census.html
new file mode 100644
index 000000000..f24050337
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_census.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test taking a census over the RDP.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ var census = yield memory.takeCensus();
+ is(typeof census, "object");
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_gc_01.html b/devtools/server/tests/mochitest/test_memory_gc_01.html
new file mode 100644
index 000000000..97cb754f0
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_gc_01.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test forcing a gc.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+
+ do {
+ var objects = [];
+ for (var i = 0; i < 1000; i++) {
+ var o = {};
+ o[Math.random()] = 1;
+ objects.push(o);
+ }
+
+ objects = null;
+
+ var { total: beforeGC } = yield memory.measure();
+
+ yield memory.forceGarbageCollection();
+ var { total: afterGC } = yield memory.measure();
+ } while(beforeGC < afterGC);
+
+ ok(true, "The amount of memory after GC should eventually decrease");
+
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_memory_gc_events.html b/devtools/server/tests/mochitest/test_memory_gc_events.html
new file mode 100644
index 000000000..2297481d4
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_memory_gc_events.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1137527 - Test receiving GC events from the memory actor.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Memory monitoring actor test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script src="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ var event = require("sdk/event/core");
+
+ Task.spawn(function* () {
+ var { memory, client } = yield startServerAndGetSelectedTabMemory();
+ yield memory.attach();
+
+ var gotGcEvent = new Promise(resolve => {
+ event.on(memory, "garbage-collection", gcData => {
+ ok(gcData, "Got GC data");
+ resolve();
+ });
+ });
+
+ memory.forceGarbageCollection();
+ yield gotGcEvent;
+
+ yield memory.detach();
+ destroyServerAndFinish(client);
+ });
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_preference.html b/devtools/server/tests/mochitest/test_preference.html
new file mode 100644
index 000000000..54903f455
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_preference.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 943251 - Allow accessing about:config from WebIDE
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test Preference Actor</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+function runTests() {
+ var Cu = Components.utils;
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var {DebuggerClient} = require("devtools/shared/client/main");
+ var {DebuggerServer} = require("devtools/server/main");
+ var Services = require("Services");
+
+ SimpleTest.waitForExplicitFinish();
+
+ var {getPreferenceFront} = require("devtools/shared/fronts/preference");
+
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(function onListTabs(aResponse) {
+ var p = getPreferenceFront(client, aResponse);
+
+ var prefs = {};
+
+ var localPref = {
+ boolPref: true,
+ intPref: 0x1234,
+ charPref: "Hello World",
+ };
+
+
+ function checkValues() {
+ is(prefs.boolPref, localPref.boolPref, "read/write bool pref");
+ is(prefs.intPref, localPref.intPref, "read/write int pref");
+ is(prefs.charPref, localPref.charPref, "read/write string pref");
+
+ ["test.all.bool", "test.all.int", "test.all.string"].forEach(function(key) {
+ var expectedValue;
+ switch(Services.prefs.getPrefType(key)) {
+ case Ci.nsIPrefBranch.PREF_STRING:
+ expectedValue = Services.prefs.getCharPref(key);
+ break;
+ case Ci.nsIPrefBranch.PREF_INT:
+ expectedValue = Services.prefs.getIntPref(key);
+ break;
+ case Ci.nsIPrefBranch.PREF_BOOL:
+ expectedValue = Services.prefs.getBoolPref(key);
+ break;
+ default:
+ ok(false, "unexpected pref type (" + key + ")");
+ break;
+ }
+
+ is(prefs.allPrefs[key].value, expectedValue, "valid preference value (" + key + ")");
+ is(prefs.allPrefs[key].hasUserValue, Services.prefs.prefHasUserValue(key), "valid hasUserValue (" + key + ")");
+ });
+
+ ["test.bool", "test.int", "test.string"].forEach(function(key) {
+ ok(!prefs.allPrefs.hasOwnProperty(key), "expect no pref (" + key + ")");
+ is(Services.prefs.getPrefType(key), Ci.nsIPrefBranch.PREF_INVALID, "pref (" + key + ") is clear");
+ });
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish()
+ });
+ }
+
+
+ p.getAllPrefs().then((json) => prefs["allPrefs"] = json)
+ .then(() => p.setBoolPref("test.bool", localPref.boolPref))
+ .then(() => p.setIntPref("test.int", localPref.intPref))
+ .then(() => p.setCharPref("test.string", localPref.charPref))
+ .then(() => p.getBoolPref("test.bool")).then((value) => prefs["boolPref"] = value)
+ .then(() => p.getIntPref("test.int")).then((value) => prefs["intPref"] = value)
+ .then(() => p.getCharPref("test.string")).then((value) => prefs["charPref"] = value)
+ .then(() => p.clearUserPref("test.bool"))
+ .then(() => p.clearUserPref("test.int"))
+ .then(() => p.clearUserPref("test.string"))
+ .then(checkValues);
+
+ });
+ });
+
+}
+
+window.onload = function () {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["devtools.debugger.forbid-certified-apps", false],
+ ["test.all.bool", true],
+ ["test.all.int", 0x4321],
+ ["test.all.string", "allizom"],
+ ]
+ }, runTests);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_settings.html b/devtools/server/tests/mochitest/test_settings.html
new file mode 100644
index 000000000..5665b46b3
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_settings.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1022797 - Settings support from WebIDE
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test Settings Actor</title>
+ <script type="text/javascript" src="chrome://mochikit/content/MochiKit/MochiKit.js"></script>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+function runTests() {
+ var Cu = Components.utils;
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+
+ var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ var {DebuggerClient} = require("devtools/shared/client/main");
+ var {DebuggerServer} = require("devtools/server/main");
+
+ if (SpecialPowers.isMainProcess()) {
+ Cu.import("resource://gre/modules/SettingsRequestManager.jsm");
+ }
+
+ SimpleTest.waitForExplicitFinish();
+
+ var {getSettingsFront} = require("devtools/shared/fronts/settings");
+ var {_setDefaultSettings} = require("devtools/server/actors/settings");
+
+ DebuggerServer.init(function () { return true; });
+ DebuggerServer.addBrowserActors();
+
+ var client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect().then(function onConnect() {
+ client.listTabs(function onListTabs(aResponse) {
+ var s = getSettingsFront(client, aResponse);
+
+ var settings = {};
+ var resetSettings = {};
+ var fakeSettings = {
+ "wifi.enabled": true,
+ "audio.volume.alarm": 15,
+ "app.reportCrashes": "ask",
+ "app.someObject": { active: true }
+ };
+ var localSetting = {
+ "wifi.enabled": false,
+ "audio.volume.alarm": 0,
+ "app.reportCrashes": "none",
+ "app.someObject": {}
+ };
+
+ function checkValues() {
+ is(settings.allSettings["wifi.enabled"].hasUserValue, false, "original unchanged bool setting");
+ is(settings.allSettings["audio.volume.alarm"].hasUserValue, false, "original unchanged int setting");
+ is(settings.allSettings["app.reportCrashes"].hasUserValue, false, "original unchanged string setting");
+ is(settings.allSettings["app.someObject"].hasUserValue, false, "original unchanged object setting");
+
+ is(settings.allSettings["wifi.enabled"].value, fakeSettings["wifi.enabled"], "original read/write bool setting");
+ is(settings.allSettings["audio.volume.alarm"].value, fakeSettings["audio.volume.alarm"], "original read/write int setting");
+ is(settings.allSettings["app.reportCrashes"].value, fakeSettings["app.reportCrashes"], "original read/write string setting");
+ is(JSON.stringify(settings.allSettings["app.someObject"].value), JSON.stringify(fakeSettings["app.someObject"]), "original read/write object setting");
+
+ is(settings.allUpdatedSettings["wifi.enabled"].hasUserValue, true, "updated user-changed bool setting");
+ is(settings.allUpdatedSettings["audio.volume.alarm"].hasUserValue, true, "updated user-changed int setting");
+ is(settings.allUpdatedSettings["app.reportCrashes"].hasUserValue, true, "updated user-changed string setting");
+ is(settings.allUpdatedSettings["app.someObject"].hasUserValue, true, "updated user-changed object setting");
+
+ is(settings["wifi.enabled"], localSetting["wifi.enabled"], "updated bool setting");
+ is(settings["audio.volume.alarm"], localSetting["audio.volume.alarm"], "updated int setting");
+ is(settings["app.reportCrashes"], localSetting["app.reportCrashes"], "updated string setting");
+ is(JSON.stringify(settings["app.someObject"]), JSON.stringify(localSetting["app.someObject"]), "updated object as string setting");
+
+ is(resetSettings["wifi.enabled"], fakeSettings["wifi.enabled"], "reset to original bool setting");
+ is(resetSettings["audio.volume.alarm"], fakeSettings["audio.volume.alarm"], "reset to original int setting");
+ is(resetSettings["app.reportCrashes"], fakeSettings["app.reportCrashes"], "reset to original string setting");
+ is(JSON.stringify(resetSettings["app.someObject"]), JSON.stringify(fakeSettings["app.someObject"]), "reset to original object setting");
+
+ client.close().then(() => {
+ DebuggerServer.destroy();
+ SimpleTest.finish();
+ });
+ }
+
+ // settings.json doesn't exist outside of b2g so we will fake it.
+ _setDefaultSettings(fakeSettings);
+ s.setSetting("wifi.enabled", fakeSettings["wifi.enabled"])
+ .then(() => s.setSetting("audio.volume.alarm", fakeSettings["audio.volume.alarm"]))
+ .then(() => s.setSetting("app.reportCrashes", fakeSettings["app.reportCrashes"]))
+ .then(() => s.setSetting("app.someObject", fakeSettings["app.someObject"]))
+ .then(() => s.getAllSettings().then(json => settings.allSettings = json))
+ .then(() => s.setSetting("wifi.enabled", localSetting["wifi.enabled"]))
+ .then(() => s.setSetting("audio.volume.alarm", localSetting["audio.volume.alarm"]))
+ .then(() => s.setSetting("app.reportCrashes", localSetting["app.reportCrashes"]))
+ .then(() => s.setSetting("app.someObject", localSetting["app.someObject"]))
+ .then(() => s.getAllSettings().then(json => settings.allUpdatedSettings = json))
+ .then(() => s.getSetting("wifi.enabled")).then(value => settings["wifi.enabled"] = value)
+ .then(() => s.getSetting("audio.volume.alarm")).then(value => settings["audio.volume.alarm"] = value)
+ .then(() => s.getSetting("app.reportCrashes")).then(value => settings["app.reportCrashes"] = value)
+ .then(() => s.getSetting("app.someObject")).then(value => settings["app.someObject"] = value)
+ .then(() => s.clearUserSetting("wifi.enabled")).then(() => {
+ s.getSetting("wifi.enabled").then(value => resetSettings["wifi.enabled"] = value);
+ })
+ .then(() => s.clearUserSetting("audio.volume.alarm")).then(() => {
+ s.getSetting("audio.volume.alarm").then(value => resetSettings["audio.volume.alarm"] = value);
+ })
+ .then(() => s.clearUserSetting("app.reportCrashes")).then(() => {
+ s.getSetting("app.reportCrashes").then(value => resetSettings["app.reportCrashes"] = value);
+ })
+ .then(() => s.clearUserSetting("app.someObject")).then(() => {
+ s.getSetting("app.someObject").then(value => {
+ resetSettings["app.someObject"] = value
+ }).then(checkValues);
+ });
+ });
+ });
+}
+
+window.onload = function () {
+ runTests();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_setupInParentChild.html b/devtools/server/tests/mochitest/test_setupInParentChild.html
new file mode 100644
index 000000000..fc94ca96a
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_setupInParentChild.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1181100 - Test DebuggerServerConnection.setupInParent and DebuggerServer.setupInChild
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript;version=1.8">
+
+let Cu = Components.utils;
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+
+let {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+let {DebuggerClient} = require("devtools/shared/client/main");
+let {DebuggerServer} = require("devtools/server/main");
+let Services = require("Services");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ // Always log packets when running tests.
+ ["devtools.debugger.log", true],
+ ["dom.mozBrowserFramesEnabled", true]
+ ]
+ }, runTests);
+}
+
+function runTests() {
+ // Create a minimal iframe with a message manager
+ let iframe = document.createElement("iframe");
+ iframe.mozbrowser = true;
+ document.body.appendChild(iframe);
+
+ let mm = iframe.frameLoader.messageManager;
+
+ // Instantiate a minimal server
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ }
+ if (!DebuggerServer.createRootActor) {
+ DebuggerServer.addBrowserActors();
+ }
+
+ // Fake a connection to an iframe
+ let transport = DebuggerServer.connectPipe();
+ let conn = transport._serverConnection;
+ let client = new DebuggerClient(transport);
+
+ // Wait for a response from setupInChild
+ const ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+ .getService(Ci.nsIMessageListenerManager);
+ let onChild = msg => {
+ ppmm.removeMessageListener("test:setupChild", onChild);
+ let args = msg.json;
+
+ is(args[0], 1, "Got first numeric argument");
+ is(args[1], "two", "Got second string argument");
+ is(args[2].three, true, "Got last JSON argument");
+
+ // Ask the child to call setupInParent
+ DebuggerServer.setupInChild({
+ module: "chrome://mochitests/content/chrome/devtools/server/tests/mochitest/setup-in-child.js",
+ setupChild: "callParent"
+ });
+ };
+ ppmm.addMessageListener("test:setupChild", onChild);
+
+ // Wait also for a reponse from setupInParent called from setup-in-child.js
+ let onParent = (_, topic, args) => {
+ Services.obs.removeObserver(onParent, "test:setupParent", false);
+ args = JSON.parse(args);
+
+ is(args[0], true, "Got `mm` argument, a message manager");
+ ok(args[1].match(/server\d+.conn\d+.child\d+/), "Got `prefix` argument");
+
+ cleanup();
+ };
+ Services.obs.addObserver(onParent, "test:setupParent", false);
+
+ // Instanciate e10s machinery and call setupInChild
+ DebuggerServer.connectToChild(conn, iframe).then(actor => {
+ DebuggerServer.setupInChild({
+ module: "chrome://mochitests/content/chrome/devtools/server/tests/mochitest/setup-in-child.js",
+ setupChild: "setupChild",
+ args: [1, "two", {three: true}]
+ });
+ });
+
+ function cleanup() {
+ client.close().then(function () {
+ DebuggerServer.destroy();
+ iframe.remove();
+ SimpleTest.finish()
+ });
+ }
+
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_styles-applied.html b/devtools/server/tests/mochitest/test_styles-applied.html
new file mode 100644
index 000000000..d9fb6ec7f
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_styles-applied.html
@@ -0,0 +1,145 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gStyles = null;
+var gClient = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ return inspector.getPageStyle();
+ }).then(styles => {
+ gStyles = styles;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function inheritedUserStyles() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
+ return gStyles.getApplied(node, { inherited: true, filter: "user" });
+ }).then(applied => {
+ ok(!applied[0].inherited, "Entry 0 should be uninherited");
+ is(applied[0].rule.type, 100, "Entry 0 should be an element style");
+ ok(!!applied[0].rule.href, "Element styles should have a URL");
+ is(applied[0].rule.cssText, "", "Entry 0 should be an empty style");
+
+ is(applied[1].inherited.id, "uninheritable-rule-inheritable-style",
+ "Entry 1 should be inherited from the parent");
+ is(applied[1].rule.type, 100, "Entry 1 should be an element style");
+ is(applied[1].rule.cssText, "color: red;", "Entry 1 should have the expected cssText");
+
+ is(applied[2].inherited.id, "inheritable-rule-inheritable-style",
+ "Entry 2 should be inherited from the parent's parent");
+ is(applied[2].rule.type, 100, "Entry 2 should be an element style");
+ is(applied[2].rule.cssText, "color: blue;", "Entry 2 should have the expected cssText");
+
+ is(applied[3].inherited.id, "inheritable-rule-inheritable-style",
+ "Entry 3 should be inherited from the parent's parent");
+ is(applied[3].rule.type, 1, "Entry 3 should be a rule style");
+ is(applied[3].rule.cssText, "font-size: 15px;", "Entry 3 should have the expected cssText");
+ ok(!applied[3].matchedSelectors, "Shouldn't get matchedSelectors with this request.");
+
+ is(applied[4].inherited.id, "inheritable-rule-uninheritable-style",
+ "Entry 4 should be inherited from the parent's parent");
+ is(applied[4].rule.type, 1, "Entry 4 should be an rule style");
+ is(applied[4].rule.cssText, "font-size: 15px;", "Entry 4 should have the expected cssText");
+ ok(!applied[4].matchedSelectors, "Shouldn't get matchedSelectors with this request.");
+
+ is(applied.length, 5, "Should have 5 rules.");
+ }).then(runNextTest));
+});
+
+addTest(function inheritedSystemStyles() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
+ return gStyles.getApplied(node, { inherited: true, filter: "ua" });
+ }).then(applied => {
+ // If our system stylesheets are prone to churn, this might be a fragile
+ // test. If you're here because of that I apologize, file a bug
+ // and we can find a different way to test.
+
+ ok(!applied[1].inherited, "Entry 1 should not be inherited");
+ ok(!applied[1].rule.parentStyleSheet.system, "Entry 1 should be a system style");
+ is(applied[1].rule.type, 1, "Entry 1 should be a rule style");
+
+ is(applied.length, 12, "Should have 12 rules.");
+ }).then(runNextTest));
+});
+
+addTest(function noInheritedStyles() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
+ return gStyles.getApplied(node, { inherited: false, filter: "user" });
+ }).then(applied => {
+ ok(!applied[0].inherited, "Entry 0 should be uninherited");
+ is(applied[0].rule.type, 100, "Entry 0 should be an element style");
+ is(applied[0].rule.cssText, "", "Entry 0 should be an empty style");
+ is(applied.length, 1, "Should have 1 rule.");
+ }).then(runNextTest));
+});
+
+addTest(function matchedSelectors() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#test-node").then(node => {
+ return gStyles.getApplied(node, {
+ inherited: true, filter: "user", matchedSelectors: true
+ });
+ }).then(applied => {
+ is(applied[3].matchedSelectors[0], ".inheritable-rule", "Entry 3 should have a matched selector");
+ is(applied[4].matchedSelectors[0], ".inheritable-rule", "Entry 4 should have a matched selector");
+ }).then(runNextTest));
+});
+
+addTest(function testMediaQuery() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#mediaqueried").then(node => {
+ return gStyles.getApplied(node, {
+ inherited: false, filter: "user", matchedSelectors: true
+ });
+ }).then(applied => {
+ is(applied[1].rule.type, 1, "Entry 1 is a rule style");
+ is(applied[1].rule.parentRule.type, 4, "Entry 1's parent rule is a media rule");
+ is(applied[1].rule.media[0], "screen", "Entry 1's rule has the expected medium");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gStyles;
+ delete gWalker;
+ delete gClient;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_styles-computed.html b/devtools/server/tests/mochitest/test_styles-computed.html
new file mode 100644
index 000000000..c70adc8eb
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_styles-computed.html
@@ -0,0 +1,139 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gStyles = null;
+var gClient = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ return inspector.getPageStyle();
+ }).then(styles => {
+ gStyles = styles;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testComputed() {
+ let localNode = gInspectee.querySelector("#computed-test-node");
+ let elementStyle = null;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, {});
+ }).then(computed => {
+ // Test a smattering of properties that include some system-defined
+ // props, some props that were defined in this node's stylesheet,
+ // and some default props.
+ is(computed["white-space"].value, "normal", "Default value should appear");
+ is(computed["display"].value, "block", "System stylesheet item should appear");
+ is(computed["cursor"].value, "crosshair", "Included stylesheet rule should appear");
+ is(computed["color"].value, "rgb(255, 0, 0)", "Inherited style attribute should appear");
+ is(computed["font-size"].value, "15px", "Inherited inline rule should appear");
+
+ // We didn't request markMatched, so these shouldn't be set
+ ok(!computed["cursor"].matched, "Didn't ask for matched, shouldn't get it");
+ ok(!computed["color"].matched, "Didn't ask for matched, shouldn't get it");
+ ok(!computed["font-size"].matched, "Didn't ask for matched, shouldn't get it");
+ }).then(runNextTest));
+});
+
+addTest(function testComputedUserMatched() {
+ let localNode = gInspectee.querySelector("#computed-test-node");
+ let elementStyle = null;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, { filter: "user", markMatched: true });
+ }).then(computed => {
+ ok(!computed["white-space"].matched, "Default style shouldn't match");
+ ok(!computed["display"].matched, "Only user styles should match");
+ ok(computed["cursor"].matched, "Asked for matched, should get it");
+ ok(computed["color"].matched, "Asked for matched, should get it");
+ ok(computed["font-size"].matched, "Asked for matched, should get it");
+ }).then(runNextTest));
+});
+
+addTest(function testComputedSystemMatched() {
+ let localNode = gInspectee.querySelector("#computed-test-node");
+ let elementStyle = null;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, { filter: "ua", markMatched: true });
+ }).then(computed => {
+ ok(!computed["white-space"].matched, "Default style shouldn't match");
+ ok(computed["display"].matched, "System stylesheets should match");
+ ok(computed["cursor"].matched, "Asked for matched, should get it");
+ ok(computed["color"].matched, "Asked for matched, should get it");
+ ok(computed["font-size"].matched, "Asked for matched, should get it");
+ }).then(runNextTest));
+});
+
+addTest(function testComputedUserOnlyMatched() {
+ let localNode = gInspectee.querySelector("#computed-test-node");
+ let elementStyle = null;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, { filter: "user", onlyMatched: true });
+ }).then(computed => {
+ ok(!("white-space" in computed), "Default style shouldn't exist");
+ ok(!("display" in computed), "System stylesheets shouldn't exist");
+ ok(("cursor" in computed), "User items should exist.");
+ ok(("color" in computed), "User items should exist.");
+ ok(("font-size" in computed), "User items should exist.");
+ }).then(runNextTest));
+});
+
+addTest(function testComputedSystemOnlyMatched() {
+ let localNode = gInspectee.querySelector("#computed-test-node");
+ let elementStyle = null;
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#computed-test-node").then(node => {
+ return gStyles.getComputed(node, { filter: "ua", onlyMatched: true });
+ }).then(computed => {
+ ok(!("white-space" in computed), "Default style shouldn't exist");
+ ok(("display" in computed), "System stylesheets should exist");
+ ok(("cursor" in computed), "User items should exist.");
+ ok(("color" in computed), "User items should exist.");
+ ok(("font-size" in computed), "User items should exist.");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gStyles;
+ delete gWalker;
+ delete gClient;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_styles-layout.html b/devtools/server/tests/mochitest/test_styles-layout.html
new file mode 100644
index 000000000..b2134b0c9
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_styles-layout.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test for Bug 1175040 - PageStyleActor.getLayout</title>
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+<script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+<script type="application/javascript;version=1.8">
+"use strict";
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+};
+
+let gWalker = null;
+let gStyles = null;
+
+addTest(function() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gWalker = walker;
+ return inspector.getPageStyle();
+ }).then(styles => {
+ gStyles = styles;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function() {
+ ok(gStyles.getLayout, "The PageStyleActor has a getLayout method");
+ runNextTest();
+});
+
+addAsyncTest(function*() {
+ let node = yield gWalker.querySelector(gWalker.rootNode, "#layout-element");
+ let layout = yield gStyles.getLayout(node, {});
+
+ let properties = ["width", "height",
+ "margin-top", "margin-right", "margin-bottom",
+ "margin-left", "padding-top", "padding-right",
+ "padding-bottom", "padding-left", "border-top-width",
+ "border-right-width", "border-bottom-width",
+ "border-left-width", "z-index", "box-sizing", "display",
+ "position"];
+ for (let prop of properties) {
+ ok((prop in layout), "The layout object returned has " + prop);
+ }
+
+ runNextTest();
+});
+
+addAsyncTest(function*() {
+ let node = yield gWalker.querySelector(gWalker.rootNode, "#layout-element");
+ let layout = yield gStyles.getLayout(node, {});
+
+ let expected = {
+ "box-sizing": "border-box",
+ "position": "absolute",
+ "z-index": "2",
+ "display": "block",
+ "width": 50,
+ "height": 50,
+ "margin-top": "10px",
+ "margin-right": "20px",
+ "margin-bottom": "30px",
+ "margin-left": "0px"
+ };
+
+ for (let name in expected) {
+ is(layout[name], expected[name], "The " + name + " property is correct");
+ }
+
+ runNextTest();
+});
+
+addAsyncTest(function*() {
+ let node = yield gWalker.querySelector(gWalker.rootNode,
+ "#layout-auto-margin-element");
+
+ let layout = yield gStyles.getLayout(node, {});
+ ok(!("autoMargins" in layout),
+ "By default, getLayout doesn't return auto margins");
+
+ layout = yield gStyles.getLayout(node, {autoMargins: true});
+ ok(("autoMargins" in layout),
+ "getLayout does return auto margins when asked to");
+ is(layout.autoMargins.left, "auto", "The left margin is auto");
+ is(layout.autoMargins.right, "auto", "The right margin is auto");
+ ok(!layout.autoMargins.bottom, "The bottom margin is not auto");
+ ok(!layout.autoMargins.top, "The top margin is not auto");
+
+ runNextTest();
+});
+
+addTest(function() {
+ gStyles = gWalker = null;
+ runNextTest();
+});
+
+</script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1175040">Mozilla Bug 1175040</a>
+<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_styles-matched.html b/devtools/server/tests/mochitest/test_styles-matched.html
new file mode 100644
index 000000000..4d24fbe70
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_styles-matched.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+const CssLogic = require("devtools/shared/inspector/css-logic");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gStyles = null;
+var gClient = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ return inspector.getPageStyle();
+ }).then(styles => {
+ gStyles = styles;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function testMatchedStyles() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#matched-test-node").then(node => {
+ return gStyles.getMatchedSelectors(node, "font-size", {});
+ }).then(matched => {
+ is(matched[0].sourceText, "this.style", "First match comes from the element style");
+ is(matched[0].selector, "@element.style", "Element style has a special selector");
+ is(matched[0].value, "10px", "First match has the expected value");
+ is(matched[0].status, CssLogic.STATUS.BEST, "First match is the best match")
+ is(matched[0].rule.type, 100, "First match is an element style");
+ is(matched[0].rule.href, gInspectee.defaultView.location.href, "Node style comes from this document")
+
+ is(matched[1].sourceText, ".inheritable-rule", "Second match comes from a rule");
+ is(matched[1].selector, ".inheritable-rule", "Second style has a selector");
+ is(matched[1].value, "15px", "Second match has the expected value");
+ is(matched[1].status, CssLogic.STATUS.PARENT_MATCH, "Second match is from the parent")
+ is(matched[1].rule.parentStyleSheet.href, null, "Inline stylesheet shouldn't have an href");
+ is(matched[1].rule.parentStyleSheet.nodeHref, gInspectee.defaultView.location.href, "Inline stylesheet's nodeHref should match the current document");
+ ok(!matched[1].rule.parentStyleSheet.system, "Inline stylesheet shouldn't be a system stylesheet.");
+ }).then(runNextTest));
+});
+
+addTest(function testSystemStyles() {
+ let testNode = null;
+
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#matched-test-node").then(node => {
+ testNode = node;
+ return gStyles.getMatchedSelectors(testNode, "display", { filter: "user" });
+ }).then(matched => {
+ is(matched.length, 0, "No user selectors apply to this rule.");
+ return gStyles.getMatchedSelectors(testNode, "display", { filter: "ua" });
+ }).then(matched => {
+ is(matched[0].selector, "div", "Should match system div selector");
+ is(matched[0].value, "block");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gStyles;
+ delete gWalker;
+ delete gClient;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_styles-modify.html b/devtools/server/tests/mochitest/test_styles-modify.html
new file mode 100644
index 000000000..5a8e20bc3
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_styles-modify.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+const {isCssPropertyKnown} = require("devtools/server/actors/css-properties");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gStyles = null;
+var gClient = null;
+
+addAsyncTest(function* setup() {
+ let url = document.getElementById("inspectorContent").href;
+ let inspector;
+
+ yield new Promise(resolve => {
+ attachURL(url, function(err, client, tab, doc) {
+ gInspectee = doc;
+ gClient = client;
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ inspector = InspectorFront(client, tab);
+ resolve();
+ });
+ });
+
+ gWalker = yield inspector.getWalker();
+ gStyles = yield inspector.getPageStyle();
+
+ runNextTest();
+});
+
+addAsyncTest(function* modifyProperties() {
+ let localNode = gInspectee.querySelector("#inheritable-rule-inheritable-style");
+
+ let node = yield gWalker.querySelector(gWalker.rootNode,
+ "#inheritable-rule-inheritable-style");
+
+ let applied = yield gStyles.getApplied(node,
+ { inherited: false, filter: "user" });
+
+ let elementStyle = applied[0].rule;
+ is(elementStyle.cssText, localNode.style.cssText, "Got expected css text");
+
+ // Change an existing property...
+ yield setProperty(elementStyle, 0, "color", "black");
+ // Create a new property
+ yield setProperty(elementStyle, 1, "background-color", "green");
+
+ // Create a new property and then change it immediately.
+ yield setProperty(elementStyle, 2, "border", "1px solid black");
+ yield setProperty(elementStyle, 2, "border", "2px solid black");
+
+ is(elementStyle.cssText,
+ "color: black; background-color: green; border: 2px solid black;",
+ "Should have expected cssText");
+ is(elementStyle.cssText, localNode.style.cssText,
+ "Local node and style front match.");
+
+ // Remove all the properties
+ yield removeProperty(elementStyle, 0, "color");
+ yield removeProperty(elementStyle, 0, "background-color");
+ yield removeProperty(elementStyle, 0, "border");
+
+ is(elementStyle.cssText, "", "Should have expected cssText");
+ is(elementStyle.cssText, localNode.style.cssText,
+ "Local node and style front match.");
+
+ runNextTest();
+});
+
+function* setProperty(rule, index, name, value) {
+ let changes = rule.startModifyingProperties(isCssPropertyKnown);
+ changes.setProperty(index, name, value);
+ yield changes.apply();
+}
+
+function* removeProperty(rule, index, name) {
+ let changes = rule.startModifyingProperties(isCssPropertyKnown);
+ changes.removeProperty(index, name);
+ yield changes.apply();
+}
+
+addTest(function cleanup() {
+ delete gStyles;
+ delete gWalker;
+ delete gClient;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug </a>
+<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_styles-svg.html b/devtools/server/tests/mochitest/test_styles-svg.html
new file mode 100644
index 000000000..51a84420c
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_styles-svg.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=921191
+Bug 921191 - allow inspection/editing of SVG elements' CSS properties
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug </title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+ <script type="application/javascript;version=1.8">
+const inspector = require("devtools/server/actors/inspector");
+
+window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+}
+
+var gWalker = null;
+var gStyles = null;
+var gClient = null;
+
+addTest(function setup() {
+ let url = document.getElementById("inspectorContent").href;
+ attachURL(url, function(err, client, tab, doc) {
+ let {InspectorFront} = require("devtools/shared/fronts/inspector");
+ let inspector = InspectorFront(client, tab);
+ promiseDone(inspector.getWalker().then(walker => {
+ ok(walker, "getWalker() should return an actor.");
+ gClient = client;
+ gWalker = walker;
+ return inspector.getPageStyle();
+ }).then(styles => {
+ gStyles = styles;
+ }).then(runNextTest));
+ });
+});
+
+addTest(function inheritedUserStyles() {
+ promiseDone(gWalker.querySelector(gWalker.rootNode, "#svgcontent rect").then(node => {
+ return gStyles.getApplied(node, { inherited: true, filter: "user" });
+ }).then(applied => {
+ is(applied.length, 2, "Should have 2 rules");
+ is(applied[1].rule.cssText, "fill: rgb(1, 2, 3);", "cssText is right");
+ }).then(runNextTest));
+});
+
+addTest(function cleanup() {
+ delete gStyles;
+ delete gWalker;
+ delete gClient;
+ runNextTest();
+});
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=921191">Mozilla Bug 921191</a>
+<a id="inspectorContent" target="_blank" href="inspector-styles-data.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_unsafeDereference.html b/devtools/server/tests/mochitest/test_unsafeDereference.html
new file mode 100644
index 000000000..df44fac51
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_unsafeDereference.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=837723
+
+When we use Debugger.Object.prototype.unsafeDereference to get a non-D.O
+reference to a content object in chrome, that reference should be via an
+xray wrapper.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug 837723</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+window.onload = function () {
+ SimpleTest.waitForExplicitFinish();
+
+ var iframe = document.createElement("iframe");
+ iframe.src = "http://mochi.test:8888/chrome/devtools/server/tests/mochitest/nonchrome_unsafeDereference.html";
+
+ iframe.onload = function () {
+ var dbg = new Debugger;
+ var contentDO = dbg.addDebuggee(iframe.contentWindow);
+ var xhrDesc = contentDO.getOwnPropertyDescriptor('xhr');
+
+ isnot(xhrDesc, undefined, "xhr should be visible as property of content global");
+ isnot(xhrDesc.value, undefined, "xhr should have a value");
+
+ var xhr = xhrDesc.value.unsafeDereference();
+
+ is(typeof xhr, "object", "we should be able to deference xhr's value's D.O");
+ is(xhr.timeout, 1742, "chrome should see the xhr's 'timeout' property");
+ is(xhr.expando, undefined, "chrome should not see the xhr's 'expando' property");
+
+ SimpleTest.finish();
+ }
+
+ document.body.appendChild(iframe);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/server/tests/mochitest/test_websocket-server.html b/devtools/server/tests/mochitest/test_websocket-server.html
new file mode 100644
index 000000000..583d96dd9
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_websocket-server.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Mozilla Bug</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+window.onload = function() {
+ const { Constructor: CC, utils: Cu } = Components;
+ const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const { Task } = require("devtools/shared/task");
+ const WebSocketServer = require("devtools/server/websocket-server");
+
+ const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+ "nsIServerSocket", "init");
+
+ add_task(function* () {
+ // Create a TCP server on auto-assigned port
+ let server = new ServerSocket(-1, true, -1);
+ ok(server, `Launched WebSocket server on port ${server.port}`);
+ server.asyncListen({
+ onSocketAccepted: Task.async(function* (socket, transport) {
+ info("Accepted incoming connection");
+ let input = transport.openInputStream(0, 0, 0);
+ let output = transport.openOutputStream(0, 0, 0);
+
+ // Perform the WebSocket handshake
+ let webSocket = yield WebSocketServer.accept(transport, input, output);
+
+ // Echo the received message back to the sender
+ webSocket.onmessage = ({ data }) => {
+ info("Server received message, echoing back");
+ webSocket.send(data);
+ };
+ }),
+
+ onStopListening(socket, status) {
+ info(`Server stopped listening with status: ${status}`);
+ }
+ });
+
+ SimpleTest.registerCleanupFunction(() => {
+ server.close();
+ });
+
+ // Create client connection
+ let client = yield new Promise((resolve, reject) => {
+ let socket = new WebSocket(`ws://localhost:${server.port}`);
+ socket.onopen = () => resolve(socket);
+ socket.onerror = reject;
+ });
+ ok(client, `Created WebSocket connection to port ${server.port}`);
+
+ // Create a promise that resolves when the WebSocket closes
+ let closed = new Promise(resolve => {
+ client.onclose = resolve;
+ });
+
+ // Send a message
+ let message = "hello there";
+ client.send(message);
+ info("Sent a message to server");
+ // Check that it was echoed
+ let echoedMessage = yield new Promise((resolve, reject) => {
+ client.onmessage = ({ data }) => resolve(data);
+ client.onerror = reject;
+ });
+
+ is(echoedMessage, message, "Echoed message matches");
+
+ // Close the connection
+ client.close();
+ yield closed;
+ });
+}
+</script>
+</body>
+</html>
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!?