summaryrefslogtreecommitdiff
path: root/browser/devtools/webconsole
diff options
context:
space:
mode:
authorPale Moon <git-repo@palemoon.org>2016-09-01 13:39:08 +0200
committerPale Moon <git-repo@palemoon.org>2016-09-01 13:39:08 +0200
commit3d8ce1a11a7347cc94a937719c4bc8df46fb8d14 (patch)
tree8c26ca375a6312751c00a27e1653fb6f189f0463 /browser/devtools/webconsole
parente449bdb1ec3a82f204bffdd9c3c54069d086eee3 (diff)
downloadpalemoon-gre-3d8ce1a11a7347cc94a937719c4bc8df46fb8d14.tar.gz
Base import of Tycho code (warning: huge commit)
Diffstat (limited to 'browser/devtools/webconsole')
-rw-r--r--browser/devtools/webconsole/Makefile.in21
-rw-r--r--browser/devtools/webconsole/console-commands.js88
-rw-r--r--browser/devtools/webconsole/console-output.js3567
-rw-r--r--browser/devtools/webconsole/hudservice.js (renamed from browser/devtools/webconsole/HUDService.jsm)546
-rw-r--r--browser/devtools/webconsole/moz.build10
-rw-r--r--browser/devtools/webconsole/network-panel.js (renamed from browser/devtools/webconsole/NetworkPanel.jsm)37
-rw-r--r--browser/devtools/webconsole/panel.js (renamed from browser/devtools/webconsole/WebConsolePanel.jsm)40
-rw-r--r--browser/devtools/webconsole/test/browser.ini380
-rw-r--r--browser/devtools/webconsole/test/browser_bug1045902_console_csp_ignore_reflected_xss_message.js59
-rw-r--r--browser/devtools/webconsole/test/browser_bug664688_sandbox_update_after_navigation.js91
-rw-r--r--browser/devtools/webconsole/test/browser_bug_638949_copy_link_location.js105
-rw-r--r--browser/devtools/webconsole/test/browser_bug_862916_console_dir_and_filter_off.js31
-rw-r--r--browser/devtools/webconsole/test/browser_bug_865288_repeat_different_objects.js63
-rw-r--r--browser/devtools/webconsole/test/browser_bug_865871_variables_view_close_on_esc_key.js98
-rw-r--r--browser/devtools/webconsole/test/browser_bug_869003_inspect_cross_domain_object.js76
-rw-r--r--browser/devtools/webconsole/test/browser_bug_871156_ctrlw_close_tab.js79
-rw-r--r--browser/devtools/webconsole/test/browser_cached_messages.js53
-rw-r--r--browser/devtools/webconsole/test/browser_console.js120
-rw-r--r--browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js90
-rw-r--r--browser/devtools/webconsole/test/browser_console_clear_on_reload.js54
-rw-r--r--browser/devtools/webconsole/test/browser_console_click_focus.js55
-rw-r--r--browser/devtools/webconsole/test/browser_console_consolejsm_output.js137
-rw-r--r--browser/devtools/webconsole/test/browser_console_copy_command.js70
-rw-r--r--browser/devtools/webconsole/test/browser_console_copy_entire_message_context_menu.js64
-rw-r--r--browser/devtools/webconsole/test/browser_console_dead_objects.js86
-rw-r--r--browser/devtools/webconsole/test/browser_console_error_source_click.js73
-rw-r--r--browser/devtools/webconsole/test/browser_console_filters.js60
-rw-r--r--browser/devtools/webconsole/test/browser_console_hide_jsterm_when_devtools_chrome_enabled_false.js105
-rw-r--r--browser/devtools/webconsole/test/browser_console_iframe_messages.js104
-rw-r--r--browser/devtools/webconsole/test/browser_console_keyboard_accessibility.js79
-rw-r--r--browser/devtools/webconsole/test/browser_console_log_inspectable_object.js50
-rw-r--r--browser/devtools/webconsole/test/browser_console_native_getters.js99
-rw-r--r--browser/devtools/webconsole/test/browser_console_navigation_marker.js75
-rw-r--r--browser/devtools/webconsole/test/browser_console_nsiconsolemessage.js80
-rw-r--r--browser/devtools/webconsole/test/browser_console_open_or_focus.js47
-rw-r--r--browser/devtools/webconsole/test/browser_console_optimized_out_vars.js82
-rw-r--r--browser/devtools/webconsole/test/browser_console_private_browsing.js200
-rw-r--r--browser/devtools/webconsole/test/browser_console_variables_view.js189
-rw-r--r--browser/devtools/webconsole/test/browser_console_variables_view_dom_nodes.js56
-rw-r--r--browser/devtools/webconsole/test/browser_console_variables_view_dont_sort_non_sortable_classes_properties.js101
-rw-r--r--browser/devtools/webconsole/test/browser_console_variables_view_highlighter.js97
-rw-r--r--browser/devtools/webconsole/test/browser_console_variables_view_while_debugging.js131
-rw-r--r--browser/devtools/webconsole/test/browser_console_variables_view_while_debugging_and_inspecting.js129
-rw-r--r--browser/devtools/webconsole/test/browser_eval_in_debugger_stackframe.js147
-rw-r--r--browser/devtools/webconsole/test/browser_eval_in_debugger_stackframe2.js63
-rw-r--r--browser/devtools/webconsole/test/browser_jsterm_inspect.js28
-rw-r--r--browser/devtools/webconsole/test/browser_longstring_hang.js53
-rw-r--r--browser/devtools/webconsole/test/browser_netpanel_longstring_expand.js307
-rw-r--r--browser/devtools/webconsole/test/browser_output_breaks_after_console_dir_uninspectable.js44
-rw-r--r--browser/devtools/webconsole/test/browser_output_longstring_expand.js83
-rw-r--r--browser/devtools/webconsole/test/browser_repeated_messages_accuracy.js125
-rw-r--r--browser/devtools/webconsole/test/browser_result_format_as_string.js43
-rw-r--r--browser/devtools/webconsole/test/browser_warn_user_about_replaced_api.js81
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_abbreviate_source_url.js21
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_allow_mixedcontent_securityerrors.js61
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_assert.js51
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js40
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_autocomplete_and_selfxss.js127
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_autocomplete_crossdomain_iframe.js59
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_autocomplete_in_debugger_stackframe.js242
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_autocomplete_popup_close_on_tab_switch.js33
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_basic_net_logging.js42
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_block_mixedcontent_securityerrors.js122
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_1006027_message_timestamps_incorrect.js41
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_1010953_cspro.js47
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_578437_page_reload.js39
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_579412_input_focus.js19
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_580001_closing_after_completion.js48
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_580030_errors_after_page_reload.js42
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_580454_timestamp_l10n.js30
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_582201_duplicate_errors.js44
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_583816_No_input_and_Tab_key_pressed.js31
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_585237_line_limit.js87
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_585956_console_trace.js48
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js376
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_popup.js119
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_586388_select_all.js91
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_587617_output_copy.js98
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_588342_document_focus.js39
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_588730_text_node_insertion.js53
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_588967_input_expansion.js45
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_589162_css_filter.js49
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_592442_closing_brackets.js37
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_593003_iframe_wrong_hud.js65
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_594477_clickable_output.js131
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_594497_history_arrow_keys.js156
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_595223_file_uri.js64
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_595350_multiple_windows_and_tabs.js101
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_595934_message_categories.js203
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_597103_deactivateHUDForContext_unfocused_window.js104
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_597136_external_script_errors.js36
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_597136_network_requests_from_chrome.js48
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_597460_filter_scroll.js82
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_597756_reopen_closed_tab.js62
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_599725_response_headers.js87
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_600183_charset.js66
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_601177_log_levels.js73
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_601352_scroll.js69
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js241
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_602572_log_bodies_checkbox.js177
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_603750_websocket.js37
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_611795.js64
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_613013_console_api_iframe.js29
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_613280_jsterm_copy.js80
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_613642_maintain_scroll.js116
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_613642_prune_scroll.js81
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_614793_jsterm_scroll.js63
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_618078_network_exceptions.js29
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_618311_close_panels.js90
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_621644_jsterm_dollar.js49
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_622303_persistent_filters.js139
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_623749_ctrl_a_select_all_winnt.js28
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_630733_response_redirect_headers.js130
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_632275_getters_document_width.js43
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js81
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_632817.js241
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_642108_pruneTest.js80
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_644419_log_limits.js225
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_646025_console_file_location.js54
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js109
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js107
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_658368_time_methods.js66
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_659907_console_dir.js28
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_660806_history_nav.js51
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_664131_console_group.js77
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_686937_autocomplete_JSTerm_helpers.js60
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_704295.js39
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_734061_No_input_change_and_Tab_key_pressed.js32
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_737873_mixedcontent.js60
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_752559_ineffective_iframe_sandbox_warning.js73
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_762593_insecure_passwords_about_blank_web_console_warning.js28
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_762593_insecure_passwords_web_console_warning.js51
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_764572_output_open_url.js137
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js78
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_770099_violation.js31
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js147
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_804845_ctrl_key_nav.js217
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_817834_add_edited_input_to_history.js63
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_837351_securityerrors.js36
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_846918_hsts_invalid-headers.js35
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_bug_915141_toggle_response_logging_with_keyboard.js113
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_cached_autocomplete.js108
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_cd_iframe.js110
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_certificate_messages.js94
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_change_font_size.js39
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_chrome.js38
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_clickable_urls.js85
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_closure_inspection.js89
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_column_numbers.js42
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_completion.js101
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_console_api_stackframe.js81
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_console_custom_styles.js79
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_console_extras.js40
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_console_logging_api.js101
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_console_trace_duplicates.js46
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_count.js77
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_dont_navigate_on_doubleclick.js50
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_execution_scope.js34
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_expandable_timestamps.js56
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_filter_buttons_contextmenu.js91
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_for_of.js27
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_history.js61
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_input_field_focus_on_panel_select.js33
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_inspect-parsed-documents.js34
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_js_input_expansion.js55
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_jsterm.js144
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_live_filtering_of_message_types.js55
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_live_filtering_on_search_strings.js96
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_log_file_filter.js82
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_message_node_id.js27
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_netlogging.js213
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_network_panel.js541
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_notifications.js77
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_open-links-without-callback.js52
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_output_01.js125
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_output_02.js160
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_output_03.js165
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_output_04.js127
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_output_05.js130
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_output_06.js127
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_output_copy_newlines.js67
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_01.js108
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_02.js111
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_03.js67
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_04.js106
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_output_events.js53
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_output_order.js47
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_output_table.js158
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_property_provider.js45
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_reflow.js32
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_scratchpad_panel_link.js63
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_show_subresource_security_errors.js30
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_split.js247
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_split_escape_key.js171
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_split_focus.js74
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_split_persist.js111
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_start_netmon_first.js37
-rw-r--r--browser/devtools/webconsole/test/browser_webconsole_view_source.js82
-rw-r--r--browser/devtools/webconsole/test/head.js1677
-rw-r--r--browser/devtools/webconsole/test/test-autocomplete-in-stackframe.html50
-rw-r--r--browser/devtools/webconsole/test/test-bug-585956-console-trace.html27
-rw-r--r--browser/devtools/webconsole/test/test-bug-593003-iframe-wrong-hud-iframe.html13
-rw-r--r--browser/devtools/webconsole/test/test-bug-593003-iframe-wrong-hud.html14
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-canvas-css.html17
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-canvas-css.js10
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-css-loader.css10
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-css-loader.css^headers^1
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-css-loader.html13
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-css-parser.css10
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-css-parser.html14
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-empty-getelementbyid.html16
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-empty-getelementbyid.js8
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-html.html16
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-image.html15
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-image.jpgbin0 -> 2532 bytes
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-imagemap.html17
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-malformedxml-external.html19
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-malformedxml-external.xml8
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-malformedxml.xhtml10
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-svg.xhtml17
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-workers.html18
-rw-r--r--browser/devtools/webconsole/test/test-bug-595934-workers.js9
-rw-r--r--browser/devtools/webconsole/test/test-bug-597136-external-script-errors.html25
-rw-r--r--browser/devtools/webconsole/test/test-bug-597136-external-script-errors.js14
-rw-r--r--browser/devtools/webconsole/test/test-bug-597756-reopen-closed-tab.html18
-rw-r--r--browser/devtools/webconsole/test/test-bug-599725-response-headers.sjs25
-rw-r--r--browser/devtools/webconsole/test/test-bug-600183-charset.html9
-rw-r--r--browser/devtools/webconsole/test/test-bug-600183-charset.html^headers^1
-rw-r--r--browser/devtools/webconsole/test/test-bug-601177-log-levels.html20
-rw-r--r--browser/devtools/webconsole/test/test-bug-601177-log-levels.js8
-rw-r--r--browser/devtools/webconsole/test/test-bug-603750-websocket.html14
-rw-r--r--browser/devtools/webconsole/test/test-bug-603750-websocket.js18
-rw-r--r--browser/devtools/webconsole/test/test-bug-609872-cd-iframe-child.html13
-rw-r--r--browser/devtools/webconsole/test/test-bug-609872-cd-iframe-parent.html14
-rw-r--r--browser/devtools/webconsole/test/test-bug-613013-console-api-iframe.html21
-rw-r--r--browser/devtools/webconsole/test/test-bug-618078-network-exceptions.html24
-rw-r--r--browser/devtools/webconsole/test/test-bug-621644-jsterm-dollar.html23
-rw-r--r--browser/devtools/webconsole/test/test-bug-630733-response-redirect-headers.sjs16
-rw-r--r--browser/devtools/webconsole/test/test-bug-632275-getters.html20
-rw-r--r--browser/devtools/webconsole/test/test-bug-632347-iterators-generators.html56
-rw-r--r--browser/devtools/webconsole/test/test-bug-644419-log-limits.html21
-rw-r--r--browser/devtools/webconsole/test/test-bug-646025-console-file-location.html12
-rw-r--r--browser/devtools/webconsole/test/test-bug-658368-time-methods.html24
-rw-r--r--browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html15
-rw-r--r--browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-inner.html13
-rw-r--r--browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-nested1.html14
-rw-r--r--browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-nested2.html14
-rw-r--r--browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning0.html13
-rw-r--r--browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning1.html13
-rw-r--r--browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning2.html13
-rw-r--r--browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning3.html14
-rw-r--r--browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning4.html14
-rw-r--r--browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning5.html14
-rw-r--r--browser/devtools/webconsole/test/test-bug-762593-insecure-passwords-about-blank-web-console-warning.html28
-rw-r--r--browser/devtools/webconsole/test/test-bug-762593-insecure-passwords-web-console-warning.html16
-rw-r--r--browser/devtools/webconsole/test/test-bug-766001-console-log.js10
-rw-r--r--browser/devtools/webconsole/test/test-bug-766001-js-console-links.html14
-rw-r--r--browser/devtools/webconsole/test/test-bug-766001-js-errors.js7
-rw-r--r--browser/devtools/webconsole/test/test-bug-782653-css-errors-1.css10
-rw-r--r--browser/devtools/webconsole/test/test-bug-782653-css-errors-2.css10
-rw-r--r--browser/devtools/webconsole/test/test-bug-782653-css-errors.html14
-rw-r--r--browser/devtools/webconsole/test/test-bug-837351-security-errors.html15
-rw-r--r--browser/devtools/webconsole/test/test-bug-846918-hsts-invalid-headers.html13
-rw-r--r--browser/devtools/webconsole/test/test-bug-846918-hsts-invalid-headers.html^headers^1
-rw-r--r--browser/devtools/webconsole/test/test-bug-859170-longstring-hang.html23
-rw-r--r--browser/devtools/webconsole/test/test-bug-869003-iframe.html20
-rw-r--r--browser/devtools/webconsole/test/test-bug-869003-top-window.html14
-rw-r--r--browser/devtools/webconsole/test/test-bug-952277-highlight-nodes-in-vview.html15
-rw-r--r--browser/devtools/webconsole/test/test-bug-989025-iframe-parent.html13
-rw-r--r--browser/devtools/webconsole/test/test-bug_923281_console_log_filter.html12
-rw-r--r--browser/devtools/webconsole/test/test-bug_923281_test1.js5
-rw-r--r--browser/devtools/webconsole/test/test-bug_923281_test2.js4
-rw-r--r--browser/devtools/webconsole/test/test-bug_939783_console_trace_duplicates.html35
-rw-r--r--browser/devtools/webconsole/test/test-certificate-messages.html22
-rw-r--r--browser/devtools/webconsole/test/test-closure-optimized-out.html34
-rw-r--r--browser/devtools/webconsole/test/test-closures.html26
-rw-r--r--browser/devtools/webconsole/test/test-console-api-stackframe.html32
-rw-r--r--browser/devtools/webconsole/test/test-console-assert.html23
-rw-r--r--browser/devtools/webconsole/test/test-console-column.html17
-rw-r--r--browser/devtools/webconsole/test/test-console-count-external-file.js7
-rw-r--r--browser/devtools/webconsole/test/test-console-count.html56
-rw-r--r--browser/devtools/webconsole/test/test-console-extras.html19
-rw-r--r--browser/devtools/webconsole/test/test-console-output-02.html61
-rw-r--r--browser/devtools/webconsole/test/test-console-output-03.html30
-rw-r--r--browser/devtools/webconsole/test/test-console-output-04.html77
-rw-r--r--browser/devtools/webconsole/test/test-console-output-dom-elements.html69
-rw-r--r--browser/devtools/webconsole/test/test-console-output-events.html42
-rw-r--r--browser/devtools/webconsole/test/test-console-replaced-api.html12
-rw-r--r--browser/devtools/webconsole/test/test-console-table.html52
-rw-r--r--browser/devtools/webconsole/test/test-console.html23
-rw-r--r--browser/devtools/webconsole/test/test-consoleiframes.html13
-rw-r--r--browser/devtools/webconsole/test/test-data.json1
-rw-r--r--browser/devtools/webconsole/test/test-data.json^headers^1
-rw-r--r--browser/devtools/webconsole/test/test-duplicate-error.html21
-rw-r--r--browser/devtools/webconsole/test/test-encoding-ISO-8859-1.html7
-rw-r--r--browser/devtools/webconsole/test/test-error.html21
-rw-r--r--browser/devtools/webconsole/test/test-eval-in-stackframe.html39
-rw-r--r--browser/devtools/webconsole/test/test-file-location.js9
-rw-r--r--browser/devtools/webconsole/test/test-filter.html11
-rw-r--r--browser/devtools/webconsole/test/test-for-of.html8
-rw-r--r--browser/devtools/webconsole/test/test-iframe-762593-insecure-form-action.html15
-rw-r--r--browser/devtools/webconsole/test/test-iframe-762593-insecure-frame.html15
-rw-r--r--browser/devtools/webconsole/test/test-iframe1.html10
-rw-r--r--browser/devtools/webconsole/test/test-iframe2.html11
-rw-r--r--browser/devtools/webconsole/test/test-iframe3.html11
-rw-r--r--browser/devtools/webconsole/test/test-image.pngbin0 -> 580 bytes
-rw-r--r--browser/devtools/webconsole/test/test-mixedcontent-securityerrors.html21
-rw-r--r--browser/devtools/webconsole/test/test-mutation.html16
-rw-r--r--browser/devtools/webconsole/test/test-network-request.html40
-rw-r--r--browser/devtools/webconsole/test/test-network.html11
-rw-r--r--browser/devtools/webconsole/test/test-observe-http-ajax.html17
-rw-r--r--browser/devtools/webconsole/test/test-own-console.html24
-rw-r--r--browser/devtools/webconsole/test/test-property-provider.html14
-rw-r--r--browser/devtools/webconsole/test/test-repeated-messages.html38
-rw-r--r--browser/devtools/webconsole/test/test-result-format-as-string.html25
-rw-r--r--browser/devtools/webconsole/test/test-webconsole-error-observer.html25
-rw-r--r--browser/devtools/webconsole/test/test_bug1045902_console_csp_ignore_reflected_xss_message.html10
-rw-r--r--browser/devtools/webconsole/test/test_bug1045902_console_csp_ignore_reflected_xss_message.html^headers^1
-rw-r--r--browser/devtools/webconsole/test/test_bug1092055_shouldwarn.html15
-rw-r--r--browser/devtools/webconsole/test/test_bug1092055_shouldwarn.js2
-rw-r--r--browser/devtools/webconsole/test/test_bug1092055_shouldwarn.js^headers^1
-rw-r--r--browser/devtools/webconsole/test/test_bug_1010953_cspro.html20
-rw-r--r--browser/devtools/webconsole/test/test_bug_1010953_cspro.html^headers^2
-rw-r--r--browser/devtools/webconsole/test/test_bug_770099_violation.html13
-rw-r--r--browser/devtools/webconsole/test/test_bug_770099_violation.html^headers^1
-rw-r--r--browser/devtools/webconsole/test/testscript.js1
-rw-r--r--browser/devtools/webconsole/webconsole.js2378
-rw-r--r--browser/devtools/webconsole/webconsole.xul211
328 files changed, 26375 insertions, 1422 deletions
diff --git a/browser/devtools/webconsole/Makefile.in b/browser/devtools/webconsole/Makefile.in
deleted file mode 100644
index 0bd782f05..000000000
--- a/browser/devtools/webconsole/Makefile.in
+++ /dev/null
@@ -1,21 +0,0 @@
-# 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/.
-
-DEPTH = @DEPTH@
-topsrcdir = @top_srcdir@
-srcdir = @srcdir@
-VPATH = @srcdir@
-
-include $(DEPTH)/config/autoconf.mk
-
-EXTRA_JS_MODULES = \
- HUDService.jsm \
- NetworkPanel.jsm \
- WebConsolePanel.jsm \
- $(NULL)
-
-include $(topsrcdir)/config/rules.mk
-
-libs::
- $(NSINSTALL) $(srcdir)/*.js $(FINAL_TARGET)/modules/devtools/framework
diff --git a/browser/devtools/webconsole/console-commands.js b/browser/devtools/webconsole/console-commands.js
new file mode 100644
index 000000000..471362c08
--- /dev/null
+++ b/browser/devtools/webconsole/console-commands.js
@@ -0,0 +1,88 @@
+/* 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/. */
+
+"use strict";
+
+const gcli = require("gcli/index");
+const { gDevTools } = require("resource:///modules/devtools/gDevTools.jsm");
+
+exports.items = [
+ {
+ name: 'splitconsole',
+ hidden: true,
+ buttonId: "command-button-splitconsole",
+ buttonClass: "command-button command-button-invertable",
+ tooltipText: gcli.lookup("splitconsoleTooltip"),
+ isRemoteSafe: true,
+ state: {
+ isChecked: function(target) {
+ let toolbox = gDevTools.getToolbox(target);
+ return !!(toolbox && toolbox.splitConsole);
+ },
+ onChange: function(target, changeHandler) {
+ // Register handlers for when a change event should be fired
+ // (which resets the checked state of the button).
+ let toolbox = gDevTools.getToolbox(target);
+ let callback = changeHandler.bind(null, "changed", { target: target });
+
+ if (!toolbox) {
+ return;
+ }
+
+ toolbox.on("split-console", callback);
+ toolbox.once("destroyed", () => {
+ toolbox.off("split-console", callback);
+ });
+ }
+ },
+ exec: function(args, context) {
+ let target = context.environment.target;
+ let toolbox = gDevTools.getToolbox(target);
+
+ if (!toolbox) {
+ return gDevTools.showToolbox(target, "inspector").then((toolbox) => {
+ toolbox.toggleSplitConsole();
+ });
+ } else {
+ toolbox.toggleSplitConsole();
+ }
+ }
+ },
+ {
+ name: "console",
+ description: gcli.lookup("consoleDesc"),
+ manual: gcli.lookup("consoleManual")
+ },
+ {
+ name: "console clear",
+ description: gcli.lookup("consoleclearDesc"),
+ exec: function(args, context) {
+ let toolbox = gDevTools.getToolbox(context.environment.target);
+ if (toolbox == null) {
+ return;
+ }
+
+ let panel = toolbox.getPanel("webconsole");
+ if (panel == null) {
+ return;
+ }
+
+ panel.hud.jsterm.clearOutput();
+ }
+ },
+ {
+ name: "console close",
+ description: gcli.lookup("consolecloseDesc"),
+ exec: function(args, context) {
+ return gDevTools.closeToolbox(context.environment.target);
+ }
+ },
+ {
+ name: "console open",
+ description: gcli.lookup("consoleopenDesc"),
+ exec: function(args, context) {
+ return gDevTools.showToolbox(context.environment.target, "webconsole");
+ }
+ }
+];
diff --git a/browser/devtools/webconsole/console-output.js b/browser/devtools/webconsole/console-output.js
new file mode 100644
index 000000000..f78e24cbd
--- /dev/null
+++ b/browser/devtools/webconsole/console-output.js
@@ -0,0 +1,3567 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+const {Cc, Ci, Cu} = require("chrome");
+
+loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
+loader.lazyImporter(this, "escapeHTML", "resource:///modules/devtools/VariablesView.jsm");
+loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
+loader.lazyImporter(this, "Task", "resource://gre/modules/Task.jsm");
+loader.lazyImporter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
+loader.lazyImporter(this, "ObjectClient", "resource://gre/modules/devtools/dbg-client.jsm");
+
+loader.lazyRequireGetter(this, "promise");
+loader.lazyRequireGetter(this, "TableWidget", "devtools/shared/widgets/TableWidget", true);
+
+const Heritage = require("sdk/core/heritage");
+const URI = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+const XHTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
+
+const WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
+const l10n = new WebConsoleUtils.l10n(STRINGS_URI);
+
+const MAX_STRING_GRIP_LENGTH = 36;
+const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
+
+// Constants for compatibility with the Web Console output implementation before
+// bug 778766.
+// TODO: remove these once bug 778766 is fixed.
+const COMPAT = {
+ // The various categories of messages.
+ CATEGORIES: {
+ NETWORK: 0,
+ CSS: 1,
+ JS: 2,
+ WEBDEV: 3,
+ INPUT: 4,
+ OUTPUT: 5,
+ SECURITY: 6,
+ },
+
+ // The possible message severities.
+ SEVERITIES: {
+ ERROR: 0,
+ WARNING: 1,
+ INFO: 2,
+ LOG: 3,
+ },
+
+ // The preference keys to use for each category/severity combination, indexed
+ // first by category (rows) and then by severity (columns).
+ //
+ // Most of these rather idiosyncratic names are historical and predate the
+ // division of message type into "category" and "severity".
+ PREFERENCE_KEYS: [
+ // Error Warning Info Log
+ [ "network", "netwarn", null, "networkinfo", ], // Network
+ [ "csserror", "cssparser", null, null, ], // CSS
+ [ "exception", "jswarn", null, "jslog", ], // JS
+ [ "error", "warn", "info", "log", ], // Web Developer
+ [ null, null, null, null, ], // Input
+ [ null, null, null, null, ], // Output
+ [ "secerror", "secwarn", null, null, ], // Security
+ ],
+
+ // The fragment of a CSS class name that identifies each category.
+ CATEGORY_CLASS_FRAGMENTS: [ "network", "cssparser", "exception", "console",
+ "input", "output", "security" ],
+
+ // The fragment of a CSS class name that identifies each severity.
+ SEVERITY_CLASS_FRAGMENTS: [ "error", "warn", "info", "log" ],
+
+ // The indent of a console group in pixels.
+ GROUP_INDENT: 12,
+};
+
+// A map from the console API call levels to the Web Console severities.
+const CONSOLE_API_LEVELS_TO_SEVERITIES = {
+ error: "error",
+ exception: "error",
+ assert: "error",
+ warn: "warning",
+ info: "info",
+ log: "log",
+ trace: "log",
+ table: "log",
+ debug: "log",
+ dir: "log",
+ group: "log",
+ groupCollapsed: "log",
+ groupEnd: "log",
+ time: "log",
+ timeEnd: "log",
+ count: "log"
+};
+
+// Array of known message source URLs we need to hide from output.
+const IGNORED_SOURCE_URLS = ["debugger eval code"];
+
+// The maximum length of strings to be displayed by the Web Console.
+const MAX_LONG_STRING_LENGTH = 200000;
+
+// Regular expression that matches the allowed CSS property names when using
+// the `window.console` API.
+const RE_ALLOWED_STYLES = /^(?:-moz-)?(?:background|border|box|clear|color|cursor|display|float|font|line|margin|padding|text|transition|outline|white-space|word|writing|(?:min-|max-)?width|(?:min-|max-)?height)/;
+
+// Regular expressions to search and replace with 'notallowed' in the styles
+// given to the `window.console` API methods.
+const RE_CLEANUP_STYLES = [
+ // url(), -moz-element()
+ /\b(?:url|(?:-moz-)?element)[\s('"]+/gi,
+
+ // various URL protocols
+ /['"(]*(?:chrome|resource|about|app|data|https?|ftp|file):+\/*/gi,
+];
+
+// Maximum number of rows to display in console.table().
+const TABLE_ROW_MAX_ITEMS = 1000;
+
+// Maximum number of columns to display in console.table().
+const TABLE_COLUMN_MAX_ITEMS = 10;
+
+/**
+ * The ConsoleOutput object is used to manage output of messages in the Web
+ * Console.
+ *
+ * @constructor
+ * @param object owner
+ * The console output owner. This usually the WebConsoleFrame instance.
+ * Any other object can be used, as long as it has the following
+ * properties and methods:
+ * - window
+ * - document
+ * - outputMessage(category, methodOrNode[, methodArguments])
+ * TODO: this is needed temporarily, until bug 778766 is fixed.
+ */
+function ConsoleOutput(owner)
+{
+ this.owner = owner;
+ this._onFlushOutputMessage = this._onFlushOutputMessage.bind(this);
+}
+
+ConsoleOutput.prototype = {
+ _dummyElement: null,
+
+ /**
+ * The output container.
+ * @type DOMElement
+ */
+ get element() {
+ return this.owner.outputNode;
+ },
+
+ /**
+ * The document that holds the output.
+ * @type DOMDocument
+ */
+ get document() {
+ return this.owner ? this.owner.document : null;
+ },
+
+ /**
+ * The DOM window that holds the output.
+ * @type Window
+ */
+ get window() {
+ return this.owner.window;
+ },
+
+ /**
+ * Getter for the debugger WebConsoleClient.
+ * @type object
+ */
+ get webConsoleClient() {
+ return this.owner.webConsoleClient;
+ },
+
+ /**
+ * Getter for the current toolbox debuggee target.
+ * @type Target
+ */
+ get toolboxTarget() {
+ return this.owner.owner.target;
+ },
+
+ /**
+ * Release an actor.
+ *
+ * @private
+ * @param string actorId
+ * The actor ID you want to release.
+ */
+ _releaseObject: function(actorId)
+ {
+ this.owner._releaseObject(actorId);
+ },
+
+ /**
+ * Add a message to output.
+ *
+ * @param object ...args
+ * Any number of Message objects.
+ * @return this
+ */
+ addMessage: function(...args)
+ {
+ for (let msg of args) {
+ msg.init(this);
+ this.owner.outputMessage(msg._categoryCompat, this._onFlushOutputMessage,
+ [msg]);
+ }
+ return this;
+ },
+
+ /**
+ * Message renderer used for compatibility with the current Web Console output
+ * implementation. This method is invoked for every message object that is
+ * flushed to output. The message object is initialized and rendered, then it
+ * is displayed.
+ *
+ * TODO: remove this method once bug 778766 is fixed.
+ *
+ * @private
+ * @param object message
+ * The message object to render.
+ * @return DOMElement
+ * The message DOM element that can be added to the console output.
+ */
+ _onFlushOutputMessage: function(message)
+ {
+ return message.render().element;
+ },
+
+ /**
+ * Get an array of selected messages. This list is based on the text selection
+ * start and end points.
+ *
+ * @param number [limit]
+ * Optional limit of selected messages you want. If no value is given,
+ * all of the selected messages are returned.
+ * @return array
+ * Array of DOM elements for each message that is currently selected.
+ */
+ getSelectedMessages: function(limit)
+ {
+ let selection = this.window.getSelection();
+ if (selection.isCollapsed) {
+ return [];
+ }
+
+ if (selection.containsNode(this.element, true)) {
+ return Array.slice(this.element.children);
+ }
+
+ let anchor = this.getMessageForElement(selection.anchorNode);
+ let focus = this.getMessageForElement(selection.focusNode);
+ if (!anchor || !focus) {
+ return [];
+ }
+
+ let start, end;
+ if (anchor.timestamp > focus.timestamp) {
+ start = focus;
+ end = anchor;
+ } else {
+ start = anchor;
+ end = focus;
+ }
+
+ let result = [];
+ let current = start;
+ while (current) {
+ result.push(current);
+ if (current == end || (limit && result.length == limit)) {
+ break;
+ }
+ current = current.nextSibling;
+ }
+ return result;
+ },
+
+ /**
+ * Find the DOM element of a message for any given descendant.
+ *
+ * @param DOMElement elem
+ * The element to start the search from.
+ * @return DOMElement|null
+ * The DOM element of the message, if any.
+ */
+ getMessageForElement: function(elem)
+ {
+ while (elem && elem.parentNode) {
+ if (elem.classList && elem.classList.contains("message")) {
+ return elem;
+ }
+ elem = elem.parentNode;
+ }
+ return null;
+ },
+
+ /**
+ * Select all messages.
+ */
+ selectAllMessages: function()
+ {
+ let selection = this.window.getSelection();
+ selection.removeAllRanges();
+ let range = this.document.createRange();
+ range.selectNodeContents(this.element);
+ selection.addRange(range);
+ },
+
+ /**
+ * Add a message to the selection.
+ *
+ * @param DOMElement elem
+ * The message element to select.
+ */
+ selectMessage: function(elem)
+ {
+ let selection = this.window.getSelection();
+ selection.removeAllRanges();
+ let range = this.document.createRange();
+ range.selectNodeContents(elem);
+ selection.addRange(range);
+ },
+
+ /**
+ * Open an URL in a new tab.
+ * @see WebConsole.openLink() in hudservice.js
+ */
+ openLink: function()
+ {
+ this.owner.owner.openLink.apply(this.owner.owner, arguments);
+ },
+
+ /**
+ * Open the variables view to inspect an object actor.
+ * @see JSTerm.openVariablesView() in webconsole.js
+ */
+ openVariablesView: function()
+ {
+ this.owner.jsterm.openVariablesView.apply(this.owner.jsterm, arguments);
+ },
+
+ /**
+ * Destroy this ConsoleOutput instance.
+ */
+ destroy: function()
+ {
+ this._dummyElement = null;
+ this.owner = null;
+ },
+}; // ConsoleOutput.prototype
+
+/**
+ * Message objects container.
+ * @type object
+ */
+let Messages = {};
+
+/**
+ * The BaseMessage object is used for all types of messages. Every kind of
+ * message should use this object as its base.
+ *
+ * @constructor
+ */
+Messages.BaseMessage = function()
+{
+ this.widgets = new Set();
+ this._onClickAnchor = this._onClickAnchor.bind(this);
+ this._repeatID = { uid: gSequenceId() };
+ this.textContent = "";
+};
+
+Messages.BaseMessage.prototype = {
+ /**
+ * Reference to the ConsoleOutput owner.
+ *
+ * @type object|null
+ * This is |null| if the message is not yet initialized.
+ */
+ output: null,
+
+ /**
+ * Reference to the parent message object, if this message is in a group or if
+ * it is otherwise owned by another message.
+ *
+ * @type object|null
+ */
+ parent: null,
+
+ /**
+ * Message DOM element.
+ *
+ * @type DOMElement|null
+ * This is |null| if the message is not yet rendered.
+ */
+ element: null,
+
+ /**
+ * Tells if this message is visible or not.
+ * @type boolean
+ */
+ get visible() {
+ return this.element && this.element.parentNode;
+ },
+
+ /**
+ * The owner DOM document.
+ * @type DOMElement
+ */
+ get document() {
+ return this.output.document;
+ },
+
+ /**
+ * Holds the text-only representation of the message.
+ * @type string
+ */
+ textContent: null,
+
+ /**
+ * Set of widgets included in this message.
+ * @type Set
+ */
+ widgets: null,
+
+ // Properties that allow compatibility with the current Web Console output
+ // implementation.
+ _categoryCompat: null,
+ _severityCompat: null,
+ _categoryNameCompat: null,
+ _severityNameCompat: null,
+ _filterKeyCompat: null,
+
+ /**
+ * Object that is JSON-ified and used as a non-unique ID for tracking
+ * duplicate messages.
+ * @private
+ * @type object
+ */
+ _repeatID: null,
+
+ /**
+ * Initialize the message.
+ *
+ * @param object output
+ * The ConsoleOutput owner.
+ * @param object [parent=null]
+ * Optional: a different message object that owns this instance.
+ * @return this
+ */
+ init: function(output, parent=null)
+ {
+ this.output = output;
+ this.parent = parent;
+ return this;
+ },
+
+ /**
+ * Non-unique ID for this message object used for tracking duplicate messages.
+ * Different message kinds can identify themselves based their own criteria.
+ *
+ * @return string
+ */
+ getRepeatID: function()
+ {
+ return JSON.stringify(this._repeatID);
+ },
+
+ /**
+ * Render the message. After this method is invoked the |element| property
+ * will point to the DOM element of this message.
+ * @return this
+ */
+ render: function()
+ {
+ if (!this.element) {
+ this.element = this._renderCompat();
+ }
+ return this;
+ },
+
+ /**
+ * Prepare the message container for the Web Console, such that it is
+ * compatible with the current implementation.
+ * TODO: remove this once bug 778766 is fixed.
+ *
+ * @private
+ * @return Element
+ * The DOM element that wraps the message.
+ */
+ _renderCompat: function()
+ {
+ let doc = this.output.document;
+ let container = doc.createElementNS(XHTML_NS, "div");
+ container.id = "console-msg-" + gSequenceId();
+ container.className = "message";
+ if (this.category == "input") {
+ // Assistive technology tools shouldn't echo input to the user,
+ // as the user knows what they've just typed.
+ container.setAttribute("aria-live", "off");
+ }
+ container.category = this._categoryCompat;
+ container.severity = this._severityCompat;
+ container.setAttribute("category", this._categoryNameCompat);
+ container.setAttribute("severity", this._severityNameCompat);
+ container.setAttribute("filter", this._filterKeyCompat);
+ container.clipboardText = this.textContent;
+ container.timestamp = this.timestamp;
+ container._messageObject = this;
+
+ return container;
+ },
+
+ /**
+ * Add a click callback to a given DOM element.
+ *
+ * @private
+ * @param Element element
+ * The DOM element to which you want to add a click event handler.
+ * @param function [callback=this._onClickAnchor]
+ * Optional click event handler. The default event handler is
+ * |this._onClickAnchor|.
+ */
+ _addLinkCallback: function(element, callback = this._onClickAnchor)
+ {
+ // This is going into the WebConsoleFrame object instance that owns
+ // the ConsoleOutput object. The WebConsoleFrame owner is the WebConsole
+ // object instance from hudservice.js.
+ // TODO: move _addMessageLinkCallback() into ConsoleOutput once bug 778766
+ // is fixed.
+ this.output.owner._addMessageLinkCallback(element, callback);
+ },
+
+ /**
+ * The default |click| event handler for links in the output. This function
+ * opens the anchor's link in a new tab.
+ *
+ * @private
+ * @param Event event
+ * The DOM event that invoked this function.
+ */
+ _onClickAnchor: function(event)
+ {
+ this.output.openLink(event.target.href);
+ },
+
+ destroy: function()
+ {
+ // Destroy all widgets that have registered themselves in this.widgets
+ for (let widget of this.widgets) {
+ widget.destroy();
+ }
+ this.widgets.clear();
+ }
+}; // Messages.BaseMessage.prototype
+
+
+/**
+ * The NavigationMarker is used to show a page load event.
+ *
+ * @constructor
+ * @extends Messages.BaseMessage
+ * @param object response
+ * The response received from the back end.
+ * @param number timestamp
+ * The message date and time, milliseconds elapsed since 1 January 1970
+ * 00:00:00 UTC.
+ */
+Messages.NavigationMarker = function(response, timestamp)
+{
+ Messages.BaseMessage.call(this);
+
+ // Store the response packet received from the server. It might
+ // be useful for extensions customizing the console output.
+ this.response = response;
+ this._url = response.url;
+ this.textContent = "------ " + this._url;
+ this.timestamp = timestamp;
+};
+
+Messages.NavigationMarker.prototype = Heritage.extend(Messages.BaseMessage.prototype,
+{
+ /**
+ * The address of the loading page.
+ * @private
+ * @type string
+ */
+ _url: null,
+
+ /**
+ * Message timestamp.
+ *
+ * @type number
+ * Milliseconds elapsed since 1 January 1970 00:00:00 UTC.
+ */
+ timestamp: 0,
+
+ _categoryCompat: COMPAT.CATEGORIES.NETWORK,
+ _severityCompat: COMPAT.SEVERITIES.LOG,
+ _categoryNameCompat: "network",
+ _severityNameCompat: "info",
+ _filterKeyCompat: "networkinfo",
+
+ /**
+ * Prepare the DOM element for this message.
+ * @return this
+ */
+ render: function()
+ {
+ if (this.element) {
+ return this;
+ }
+
+ let url = this._url;
+ let pos = url.indexOf("?");
+ if (pos > -1) {
+ url = url.substr(0, pos);
+ }
+
+ let doc = this.output.document;
+ let urlnode = doc.createElementNS(XHTML_NS, "a");
+ urlnode.className = "url";
+ urlnode.textContent = url;
+ urlnode.title = this._url;
+ urlnode.href = this._url;
+ urlnode.draggable = false;
+ this._addLinkCallback(urlnode);
+
+ let render = Messages.BaseMessage.prototype.render.bind(this);
+ render().element.appendChild(urlnode);
+ this.element.classList.add("navigation-marker");
+ this.element.url = this._url;
+ this.element.appendChild(doc.createTextNode("\n"));
+
+ return this;
+ },
+}); // Messages.NavigationMarker.prototype
+
+
+/**
+ * The Simple message is used to show any basic message in the Web Console.
+ *
+ * @constructor
+ * @extends Messages.BaseMessage
+ * @param string|Node|function message
+ * The message to display.
+ * @param object [options]
+ * Options for this message:
+ * - category: (string) category that this message belongs to. Defaults
+ * to no category.
+ * - severity: (string) severity of the message. Defaults to no severity.
+ * - timestamp: (number) date and time when the message was recorded.
+ * Defaults to |Date.now()|.
+ * - link: (string) if provided, the message will be wrapped in an anchor
+ * pointing to the given URL here.
+ * - linkCallback: (function) if provided, the message will be wrapped in
+ * an anchor. The |linkCallback| function will be added as click event
+ * handler.
+ * - location: object that tells the message source: url, line, column
+ * and lineText.
+ * - className: (string) additional element class names for styling
+ * purposes.
+ * - private: (boolean) mark this as a private message.
+ * - filterDuplicates: (boolean) true if you do want this message to be
+ * filtered as a potential duplicate message, false otherwise.
+ */
+Messages.Simple = function(message, options = {})
+{
+ Messages.BaseMessage.call(this);
+
+ this.category = options.category;
+ this.severity = options.severity;
+ this.location = options.location;
+ this.timestamp = options.timestamp || Date.now();
+ this.private = !!options.private;
+
+ this._message = message;
+ this._className = options.className;
+ this._link = options.link;
+ this._linkCallback = options.linkCallback;
+ this._filterDuplicates = options.filterDuplicates;
+};
+
+Messages.Simple.prototype = Heritage.extend(Messages.BaseMessage.prototype,
+{
+ /**
+ * Message category.
+ * @type string
+ */
+ category: null,
+
+ /**
+ * Message severity.
+ * @type string
+ */
+ severity: null,
+
+ /**
+ * Message source location. Properties: url, line, column, lineText.
+ * @type object
+ */
+ location: null,
+
+ /**
+ * Tells if this message comes from a private browsing context.
+ * @type boolean
+ */
+ private: false,
+
+ /**
+ * Custom class name for the DOM element of the message.
+ * @private
+ * @type string
+ */
+ _className: null,
+
+ /**
+ * Message link - if this message is clicked then this URL opens in a new tab.
+ * @private
+ * @type string
+ */
+ _link: null,
+
+ /**
+ * Message click event handler.
+ * @private
+ * @type function
+ */
+ _linkCallback: null,
+
+ /**
+ * Tells if this message should be checked if it is a duplicate of another
+ * message or not.
+ */
+ _filterDuplicates: false,
+
+ /**
+ * The raw message displayed by this Message object. This can be a function,
+ * DOM node or a string.
+ *
+ * @private
+ * @type mixed
+ */
+ _message: null,
+
+ _afterMessage: null,
+ _objectActors: null,
+ _groupDepthCompat: 0,
+
+ /**
+ * Message timestamp.
+ *
+ * @type number
+ * Milliseconds elapsed since 1 January 1970 00:00:00 UTC.
+ */
+ timestamp: 0,
+
+ get _categoryCompat() {
+ return this.category ?
+ COMPAT.CATEGORIES[this.category.toUpperCase()] : null;
+ },
+ get _severityCompat() {
+ return this.severity ?
+ COMPAT.SEVERITIES[this.severity.toUpperCase()] : null;
+ },
+ get _categoryNameCompat() {
+ return this.category ?
+ COMPAT.CATEGORY_CLASS_FRAGMENTS[this._categoryCompat] : null;
+ },
+ get _severityNameCompat() {
+ return this.severity ?
+ COMPAT.SEVERITY_CLASS_FRAGMENTS[this._severityCompat] : null;
+ },
+
+ get _filterKeyCompat() {
+ return this._categoryCompat !== null && this._severityCompat !== null ?
+ COMPAT.PREFERENCE_KEYS[this._categoryCompat][this._severityCompat] :
+ null;
+ },
+
+ init: function()
+ {
+ Messages.BaseMessage.prototype.init.apply(this, arguments);
+ this._groupDepthCompat = this.output.owner.groupDepth;
+ this._initRepeatID();
+ return this;
+ },
+
+ _initRepeatID: function()
+ {
+ if (!this._filterDuplicates) {
+ return;
+ }
+
+ // Add the properties we care about for identifying duplicate messages.
+ let rid = this._repeatID;
+ delete rid.uid;
+
+ rid.category = this.category;
+ rid.severity = this.severity;
+ rid.private = this.private;
+ rid.location = this.location;
+ rid.link = this._link;
+ rid.linkCallback = this._linkCallback + "";
+ rid.className = this._className;
+ rid.groupDepth = this._groupDepthCompat;
+ rid.textContent = "";
+ },
+
+ getRepeatID: function()
+ {
+ // No point in returning a string that includes other properties when there
+ // is a unique ID.
+ if (this._repeatID.uid) {
+ return JSON.stringify({ uid: this._repeatID.uid });
+ }
+
+ return JSON.stringify(this._repeatID);
+ },
+
+ render: function()
+ {
+ if (this.element) {
+ return this;
+ }
+
+ let timestamp = new Widgets.MessageTimestamp(this, this.timestamp).render();
+
+ let icon = this.document.createElementNS(XHTML_NS, "span");
+ icon.className = "icon";
+ icon.title = l10n.getStr("severity." + this._severityNameCompat);
+
+ // Apply the current group by indenting appropriately.
+ // TODO: remove this once bug 778766 is fixed.
+ let indent = this._groupDepthCompat * COMPAT.GROUP_INDENT;
+ let indentNode = this.document.createElementNS(XHTML_NS, "span");
+ indentNode.className = "indent";
+ indentNode.style.width = indent + "px";
+
+ let body = this._renderBody();
+ this._repeatID.textContent += "|" + body.textContent;
+
+ let repeatNode = this._renderRepeatNode();
+ let location = this._renderLocation();
+
+ Messages.BaseMessage.prototype.render.call(this);
+ if (this._className) {
+ this.element.className += " " + this._className;
+ }
+
+ this.element.appendChild(timestamp.element);
+ this.element.appendChild(indentNode);
+ this.element.appendChild(icon);
+ this.element.appendChild(body);
+ if (repeatNode) {
+ this.element.appendChild(repeatNode);
+ }
+ if (location) {
+ this.element.appendChild(location);
+ }
+ this.element.appendChild(this.document.createTextNode("\n"));
+
+ this.element.clipboardText = this.element.textContent;
+
+ if (this.private) {
+ this.element.setAttribute("private", true);
+ }
+
+ if (this._afterMessage) {
+ this.element._outputAfterNode = this._afterMessage.element;
+ this._afterMessage = null;
+ }
+
+ // TODO: handle object releasing in a more elegant way once all console
+ // messages use the new API - bug 778766.
+ this.element._objectActors = this._objectActors;
+ this._objectActors = null;
+
+ return this;
+ },
+
+ /**
+ * Render the message body DOM element.
+ * @private
+ * @return Element
+ */
+ _renderBody: function()
+ {
+ let body = this.document.createElementNS(XHTML_NS, "span");
+ body.className = "message-body-wrapper message-body devtools-monospace";
+
+ let bodyInner = this.document.createElementNS(XHTML_NS, "span");
+ body.appendChild(bodyInner);
+
+ let anchor, container = bodyInner;
+ if (this._link || this._linkCallback) {
+ container = anchor = this.document.createElementNS(XHTML_NS, "a");
+ anchor.href = this._link || "#";
+ anchor.draggable = false;
+ this._addLinkCallback(anchor, this._linkCallback);
+ bodyInner.appendChild(anchor);
+ }
+
+ if (typeof this._message == "function") {
+ container.appendChild(this._message(this));
+ } else if (this._message instanceof Ci.nsIDOMNode) {
+ container.appendChild(this._message);
+ } else {
+ container.textContent = this._message;
+ }
+
+ return body;
+ },
+
+ /**
+ * Render the repeat bubble DOM element part of the message.
+ * @private
+ * @return Element
+ */
+ _renderRepeatNode: function()
+ {
+ if (!this._filterDuplicates) {
+ return null;
+ }
+
+ let repeatNode = this.document.createElementNS(XHTML_NS, "span");
+ repeatNode.setAttribute("value", "1");
+ repeatNode.className = "message-repeats";
+ repeatNode.textContent = 1;
+ repeatNode._uid = this.getRepeatID();
+ return repeatNode;
+ },
+
+ /**
+ * Render the message source location DOM element.
+ * @private
+ * @return Element
+ */
+ _renderLocation: function()
+ {
+ if (!this.location) {
+ return null;
+ }
+
+ let {url, line, column} = this.location;
+ if (IGNORED_SOURCE_URLS.indexOf(url) != -1) {
+ return null;
+ }
+
+ // The ConsoleOutput owner is a WebConsoleFrame instance from webconsole.js.
+ // TODO: move createLocationNode() into this file when bug 778766 is fixed.
+ return this.output.owner.createLocationNode({url: url,
+ line: line,
+ column: column});
+ },
+}); // Messages.Simple.prototype
+
+
+/**
+ * The Extended message.
+ *
+ * @constructor
+ * @extends Messages.Simple
+ * @param array messagePieces
+ * The message to display given as an array of elements. Each array
+ * element can be a DOM node, function, ObjectActor, LongString or
+ * a string.
+ * @param object [options]
+ * Options for rendering this message:
+ * - quoteStrings: boolean that tells if you want strings to be wrapped
+ * in quotes or not.
+ */
+Messages.Extended = function(messagePieces, options = {})
+{
+ Messages.Simple.call(this, null, options);
+
+ this._messagePieces = messagePieces;
+
+ if ("quoteStrings" in options) {
+ this._quoteStrings = options.quoteStrings;
+ }
+
+ this._repeatID.quoteStrings = this._quoteStrings;
+ this._repeatID.messagePieces = messagePieces + "";
+ this._repeatID.actors = new Set(); // using a set to avoid duplicates
+};
+
+Messages.Extended.prototype = Heritage.extend(Messages.Simple.prototype,
+{
+ /**
+ * The message pieces displayed by this message instance.
+ * @private
+ * @type array
+ */
+ _messagePieces: null,
+
+ /**
+ * Boolean that tells if the strings displayed in this message are wrapped.
+ * @private
+ * @type boolean
+ */
+ _quoteStrings: true,
+
+ getRepeatID: function()
+ {
+ if (this._repeatID.uid) {
+ return JSON.stringify({ uid: this._repeatID.uid });
+ }
+
+ // Sets are not stringified correctly. Temporarily switching to an array.
+ let actors = this._repeatID.actors;
+ this._repeatID.actors = [...actors];
+ let result = JSON.stringify(this._repeatID);
+ this._repeatID.actors = actors;
+ return result;
+ },
+
+ render: function()
+ {
+ let result = this.document.createDocumentFragment();
+
+ for (let i = 0; i < this._messagePieces.length; i++) {
+ let separator = i > 0 ? this._renderBodyPieceSeparator() : null;
+ if (separator) {
+ result.appendChild(separator);
+ }
+
+ let piece = this._messagePieces[i];
+ result.appendChild(this._renderBodyPiece(piece));
+ }
+
+ this._message = result;
+ this._messagePieces = null;
+ return Messages.Simple.prototype.render.call(this);
+ },
+
+ /**
+ * Render the separator between the pieces of the message.
+ *
+ * @private
+ * @return Element
+ */
+ _renderBodyPieceSeparator: function() { return null; },
+
+ /**
+ * Render one piece/element of the message array.
+ *
+ * @private
+ * @param mixed piece
+ * Message element to display - this can be a LongString, ObjectActor,
+ * DOM node or a function to invoke.
+ * @return Element
+ */
+ _renderBodyPiece: function(piece)
+ {
+ if (piece instanceof Ci.nsIDOMNode) {
+ return piece;
+ }
+ if (typeof piece == "function") {
+ return piece(this);
+ }
+
+ return this._renderValueGrip(piece);
+ },
+
+ /**
+ * Render a grip that represents a value received from the server. This method
+ * picks the appropriate widget to render the value with.
+ *
+ * @private
+ * @param object grip
+ * The value grip received from the server.
+ * @param object options
+ * Options for displaying the value. Available options:
+ * - noStringQuotes - boolean that tells the renderer to not use quotes
+ * around strings.
+ * - concise - boolean that tells the renderer to compactly display the
+ * grip. This is typically set to true when the object needs to be
+ * displayed in an array preview, or as a property value in object
+ * previews, etc.
+ * @return DOMElement
+ * The DOM element that displays the given grip.
+ */
+ _renderValueGrip: function(grip, options = {})
+ {
+ let isPrimitive = VariablesView.isPrimitive({ value: grip });
+ let isActorGrip = WebConsoleUtils.isActorGrip(grip);
+ let noStringQuotes = !this._quoteStrings;
+ if ("noStringQuotes" in options) {
+ noStringQuotes = options.noStringQuotes;
+ }
+
+ if (isActorGrip) {
+ this._repeatID.actors.add(grip.actor);
+
+ if (!isPrimitive) {
+ return this._renderObjectActor(grip, options);
+ }
+ if (grip.type == "longString") {
+ let widget = new Widgets.LongString(this, grip, options).render();
+ return widget.element;
+ }
+ }
+
+ let result = this.document.createElementNS(XHTML_NS, "span");
+ if (isPrimitive) {
+ if (Widgets.URLString.prototype.containsURL.call(Widgets.URLString.prototype, grip)) {
+ let widget = new Widgets.URLString(this, grip, options).render();
+ return widget.element;
+ }
+
+ let className = this.getClassNameForValueGrip(grip);
+ if (className) {
+ result.className = className;
+ }
+
+ result.textContent = VariablesView.getString(grip, {
+ noStringQuotes: noStringQuotes,
+ concise: options.concise,
+ });
+ } else {
+ result.textContent = grip;
+ }
+
+ return result;
+ },
+
+ /**
+ * Shorten grips of the type string, leaves other grips unmodified.
+ *
+ * @param object grip
+ * Value grip from the server.
+ * @return object
+ * Possible values of object:
+ * - A shortened string, if original grip was of string type.
+ * - The unmodified input grip, if it wasn't of string type.
+ */
+ shortenValueGrip: function(grip)
+ {
+ let shortVal = grip;
+ if (typeof(grip)=="string") {
+ shortVal = grip.replace(/(\r\n|\n|\r)/gm," ");
+ if (shortVal.length > MAX_STRING_GRIP_LENGTH) {
+ shortVal = shortVal.substring(0,MAX_STRING_GRIP_LENGTH - 1) + ELLIPSIS;
+ }
+ }
+
+ return shortVal;
+ },
+
+ /**
+ * Get a CodeMirror-compatible class name for a given value grip.
+ *
+ * @param object grip
+ * Value grip from the server.
+ * @return string
+ * The class name for the grip.
+ */
+ getClassNameForValueGrip: function(grip)
+ {
+ let map = {
+ "number": "cm-number",
+ "longstring": "console-string",
+ "string": "console-string",
+ "regexp": "cm-string-2",
+ "boolean": "cm-atom",
+ "-infinity": "cm-atom",
+ "infinity": "cm-atom",
+ "null": "cm-atom",
+ "undefined": "cm-comment",
+ "symbol": "cm-atom"
+ };
+
+ let className = map[typeof grip];
+ if (!className && grip && grip.type) {
+ className = map[grip.type.toLowerCase()];
+ }
+ if (!className && grip && grip.class) {
+ className = map[grip.class.toLowerCase()];
+ }
+
+ return className;
+ },
+
+ /**
+ * Display an object actor with the appropriate renderer.
+ *
+ * @private
+ * @param object objectActor
+ * The ObjectActor to display.
+ * @param object options
+ * Options to use for displaying the ObjectActor.
+ * @see this._renderValueGrip for the available options.
+ * @return DOMElement
+ * The DOM element that displays the object actor.
+ */
+ _renderObjectActor: function(objectActor, options = {})
+ {
+ let widget = Widgets.ObjectRenderers.byClass[objectActor.class];
+
+ let { preview } = objectActor;
+ if ((!widget || (widget.canRender && !widget.canRender(objectActor)))
+ && preview
+ && preview.kind) {
+ widget = Widgets.ObjectRenderers.byKind[preview.kind];
+ }
+
+ if (!widget || (widget.canRender && !widget.canRender(objectActor))) {
+ widget = Widgets.JSObject;
+ }
+
+ let instance = new widget(this, objectActor, options).render();
+ return instance.element;
+ },
+}); // Messages.Extended.prototype
+
+
+
+/**
+ * The JavaScriptEvalOutput message.
+ *
+ * @constructor
+ * @extends Messages.Extended
+ * @param object evalResponse
+ * The evaluation response packet received from the server.
+ * @param string [errorMessage]
+ * Optional error message to display.
+ */
+Messages.JavaScriptEvalOutput = function(evalResponse, errorMessage)
+{
+ let severity = "log", msg, quoteStrings = true;
+
+ // Store also the response packet from the back end. It might
+ // be useful to extensions customizing the console output.
+ this.response = evalResponse;
+
+ if (errorMessage) {
+ severity = "error";
+ msg = errorMessage;
+ quoteStrings = false;
+ } else {
+ msg = evalResponse.result;
+ }
+
+ let options = {
+ className: "cm-s-mozilla",
+ timestamp: evalResponse.timestamp,
+ category: "output",
+ severity: severity,
+ quoteStrings: quoteStrings,
+ };
+ Messages.Extended.call(this, [msg], options);
+};
+
+Messages.JavaScriptEvalOutput.prototype = Messages.Extended.prototype;
+
+/**
+ * The ConsoleGeneric message is used for console API calls.
+ *
+ * @constructor
+ * @extends Messages.Extended
+ * @param object packet
+ * The Console API call packet received from the server.
+ */
+Messages.ConsoleGeneric = function(packet)
+{
+ let options = {
+ className: "cm-s-mozilla",
+ timestamp: packet.timeStamp,
+ category: "webdev",
+ severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level],
+ private: packet.private,
+ filterDuplicates: true,
+ location: {
+ url: packet.filename,
+ line: packet.lineNumber,
+ column: packet.columnNumber
+ },
+ };
+
+ switch (packet.level) {
+ case "count": {
+ let counter = packet.counter, label = counter.label;
+ if (!label) {
+ label = l10n.getStr("noCounterLabel");
+ }
+ Messages.Extended.call(this, [label+ ": " + counter.count], options);
+ break;
+ }
+ default:
+ Messages.Extended.call(this, packet.arguments, options);
+ break;
+ }
+
+ this._repeatID.consoleApiLevel = packet.level;
+ this._repeatID.styles = packet.styles;
+ this._stacktrace = this._repeatID.stacktrace = packet.stacktrace;
+ this._styles = packet.styles || [];
+
+ this._onClickCollapsible = this._onClickCollapsible.bind(this);
+};
+
+Messages.ConsoleGeneric.prototype = Heritage.extend(Messages.Extended.prototype,
+{
+ _styles: null,
+ _stacktrace: null,
+
+ /**
+ * Tells if the message can be expanded/collapsed.
+ * @type boolean
+ */
+ collapsible: false,
+
+ /**
+ * Getter that tells if this message is collapsed - no details are shown.
+ * @type boolean
+ */
+ get collapsed() {
+ return this.collapsible && this.element && !this.element.hasAttribute("open");
+ },
+
+ _renderBodyPieceSeparator: function()
+ {
+ return this.document.createTextNode(" ");
+ },
+
+ render: function()
+ {
+ let msg = this.document.createElementNS(XHTML_NS, "span");
+ msg.className = "message-body devtools-monospace";
+
+ this._renderBodyPieces(msg);
+
+ let repeatNode = Messages.Simple.prototype._renderRepeatNode.call(this);
+ let location = Messages.Simple.prototype._renderLocation.call(this);
+ if (location) {
+ location.target = "jsdebugger";
+ }
+
+ let stack = null;
+ let twisty = null;
+ if (this._stacktrace && this._stacktrace.length > 0) {
+ stack = new Widgets.Stacktrace(this, this._stacktrace).render().element;
+
+ twisty = this.document.createElementNS(XHTML_NS, "a");
+ twisty.className = "theme-twisty";
+ twisty.href = "#";
+ twisty.title = l10n.getStr("messageToggleDetails");
+ twisty.addEventListener("click", this._onClickCollapsible);
+ }
+
+ let flex = this.document.createElementNS(XHTML_NS, "span");
+ flex.className = "message-flex-body";
+
+ if (twisty) {
+ flex.appendChild(twisty);
+ }
+
+ flex.appendChild(msg);
+
+ if (repeatNode) {
+ flex.appendChild(repeatNode);
+ }
+ if (location) {
+ flex.appendChild(location);
+ }
+
+ let result = this.document.createDocumentFragment();
+ result.appendChild(flex);
+
+ if (stack) {
+ result.appendChild(this.document.createTextNode("\n"));
+ result.appendChild(stack);
+ }
+
+ this._message = result;
+ this._stacktrace = null;
+
+ Messages.Simple.prototype.render.call(this);
+
+ if (stack) {
+ this.collapsible = true;
+ this.element.setAttribute("collapsible", true);
+
+ let icon = this.element.querySelector(".icon");
+ icon.addEventListener("click", this._onClickCollapsible);
+ }
+
+ return this;
+ },
+
+ _renderBody: function()
+ {
+ let body = Messages.Simple.prototype._renderBody.apply(this, arguments);
+ body.classList.remove("devtools-monospace", "message-body");
+ return body;
+ },
+
+ _renderBodyPieces: function(container)
+ {
+ let lastStyle = null;
+
+ for (let i = 0; i < this._messagePieces.length; i++) {
+ let separator = i > 0 ? this._renderBodyPieceSeparator() : null;
+ if (separator) {
+ container.appendChild(separator);
+ }
+
+ let piece = this._messagePieces[i];
+ let style = this._styles[i];
+
+ // No long string support.
+ if (style && typeof style == "string" ) {
+ lastStyle = this.cleanupStyle(style);
+ }
+
+ container.appendChild(this._renderBodyPiece(piece, lastStyle));
+ }
+
+ this._messagePieces = null;
+ this._styles = null;
+ },
+
+ _renderBodyPiece: function(piece, style)
+ {
+ let elem = Messages.Extended.prototype._renderBodyPiece.call(this, piece);
+ let result = elem;
+
+ if (style) {
+ if (elem.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
+ elem.style = style;
+ } else {
+ let span = this.document.createElementNS(XHTML_NS, "span");
+ span.style = style;
+ span.appendChild(elem);
+ result = span;
+ }
+ }
+
+ return result;
+ },
+
+ // no-op for the message location and .repeats elements.
+ // |this.render()| handles customized message output.
+ _renderLocation: function() { },
+ _renderRepeatNode: function() { },
+
+ /**
+ * Expand/collapse message details.
+ */
+ toggleDetails: function()
+ {
+ let twisty = this.element.querySelector(".theme-twisty");
+ if (this.element.hasAttribute("open")) {
+ this.element.removeAttribute("open");
+ twisty.removeAttribute("open");
+ } else {
+ this.element.setAttribute("open", true);
+ twisty.setAttribute("open", true);
+ }
+ },
+
+ /**
+ * The click event handler for the message expander arrow element. This method
+ * toggles the display of message details.
+ *
+ * @private
+ * @param nsIDOMEvent ev
+ * The DOM event object.
+ * @see this.toggleDetails()
+ */
+ _onClickCollapsible: function(ev)
+ {
+ ev.preventDefault();
+ this.toggleDetails();
+ },
+
+ /**
+ * Given a style attribute value, return a cleaned up version of the string
+ * such that:
+ *
+ * - no external URL is allowed to load. See RE_CLEANUP_STYLES.
+ * - only some of the properties are allowed, based on a whitelist. See
+ * RE_ALLOWED_STYLES.
+ *
+ * @param string style
+ * The style string to cleanup.
+ * @return string
+ * The style value after cleanup.
+ */
+ cleanupStyle: function(style)
+ {
+ for (let r of RE_CLEANUP_STYLES) {
+ style = style.replace(r, "notallowed");
+ }
+
+ let dummy = this.output._dummyElement;
+ if (!dummy) {
+ dummy = this.output._dummyElement =
+ this.document.createElementNS(XHTML_NS, "div");
+ }
+ dummy.style = style;
+
+ let toRemove = [];
+ for (let i = 0; i < dummy.style.length; i++) {
+ let prop = dummy.style[i];
+ if (!RE_ALLOWED_STYLES.test(prop)) {
+ toRemove.push(prop);
+ }
+ }
+
+ for (let prop of toRemove) {
+ dummy.style.removeProperty(prop);
+ }
+
+ style = dummy.style.cssText;
+
+ dummy.style = "";
+
+ return style;
+ },
+}); // Messages.ConsoleGeneric.prototype
+
+/**
+ * The ConsoleTrace message is used for console.trace() calls.
+ *
+ * @constructor
+ * @extends Messages.Simple
+ * @param object packet
+ * The Console API call packet received from the server.
+ */
+Messages.ConsoleTrace = function(packet)
+{
+ let options = {
+ className: "cm-s-mozilla",
+ timestamp: packet.timeStamp,
+ category: "webdev",
+ severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level],
+ private: packet.private,
+ filterDuplicates: true,
+ location: {
+ url: packet.filename,
+ line: packet.lineNumber,
+ },
+ };
+
+ this._renderStack = this._renderStack.bind(this);
+ Messages.Simple.call(this, this._renderStack, options);
+
+ this._repeatID.consoleApiLevel = packet.level;
+ this._stacktrace = this._repeatID.stacktrace = packet.stacktrace;
+ this._arguments = packet.arguments;
+};
+
+Messages.ConsoleTrace.prototype = Heritage.extend(Messages.Simple.prototype,
+{
+ /**
+ * Holds the stackframes received from the server.
+ *
+ * @private
+ * @type array
+ */
+ _stacktrace: null,
+
+ /**
+ * Holds the arguments the content script passed to the console.trace()
+ * method. This array is cleared when the message is initialized, and
+ * associated actors are released.
+ *
+ * @private
+ * @type array
+ */
+ _arguments: null,
+
+ init: function()
+ {
+ let result = Messages.Simple.prototype.init.apply(this, arguments);
+
+ // We ignore console.trace() arguments. Release object actors.
+ if (Array.isArray(this._arguments)) {
+ for (let arg of this._arguments) {
+ if (WebConsoleUtils.isActorGrip(arg)) {
+ this.output._releaseObject(arg.actor);
+ }
+ }
+ }
+ this._arguments = null;
+
+ return result;
+ },
+
+ render: function()
+ {
+ Messages.Simple.prototype.render.apply(this, arguments);
+ this.element.setAttribute("open", true);
+ return this;
+ },
+
+ /**
+ * Render the stack frames.
+ *
+ * @private
+ * @return DOMElement
+ */
+ _renderStack: function()
+ {
+ let cmvar = this.document.createElementNS(XHTML_NS, "span");
+ cmvar.className = "cm-variable";
+ cmvar.textContent = "console";
+
+ let cmprop = this.document.createElementNS(XHTML_NS, "span");
+ cmprop.className = "cm-property";
+ cmprop.textContent = "trace";
+
+ let title = this.document.createElementNS(XHTML_NS, "span");
+ title.className = "message-body devtools-monospace";
+ title.appendChild(cmvar);
+ title.appendChild(this.document.createTextNode("."));
+ title.appendChild(cmprop);
+ title.appendChild(this.document.createTextNode("():"));
+
+ let repeatNode = Messages.Simple.prototype._renderRepeatNode.call(this);
+ let location = Messages.Simple.prototype._renderLocation.call(this);
+ if (location) {
+ location.target = "jsdebugger";
+ }
+
+ let widget = new Widgets.Stacktrace(this, this._stacktrace).render();
+
+ let body = this.document.createElementNS(XHTML_NS, "span");
+ body.className = "message-flex-body";
+ body.appendChild(title);
+ if (repeatNode) {
+ body.appendChild(repeatNode);
+ }
+ if (location) {
+ body.appendChild(location);
+ }
+ body.appendChild(this.document.createTextNode("\n"));
+
+ let frag = this.document.createDocumentFragment();
+ frag.appendChild(body);
+ frag.appendChild(widget.element);
+
+ return frag;
+ },
+
+ _renderBody: function()
+ {
+ let body = Messages.Simple.prototype._renderBody.apply(this, arguments);
+ body.classList.remove("devtools-monospace", "message-body");
+ return body;
+ },
+
+ // no-op for the message location and .repeats elements.
+ // |this._renderStack| handles customized message output.
+ _renderLocation: function() { },
+ _renderRepeatNode: function() { },
+}); // Messages.ConsoleTrace.prototype
+
+/**
+ * The ConsoleTable message is used for console.table() calls.
+ *
+ * @constructor
+ * @extends Messages.Extended
+ * @param object packet
+ * The Console API call packet received from the server.
+ */
+Messages.ConsoleTable = function(packet)
+{
+ let options = {
+ className: "cm-s-mozilla",
+ timestamp: packet.timeStamp,
+ category: "webdev",
+ severity: CONSOLE_API_LEVELS_TO_SEVERITIES[packet.level],
+ private: packet.private,
+ filterDuplicates: false,
+ location: {
+ url: packet.filename,
+ line: packet.lineNumber,
+ },
+ };
+
+ this._populateTableData = this._populateTableData.bind(this);
+ this._renderTable = this._renderTable.bind(this);
+ Messages.Extended.call(this, [this._renderTable], options);
+
+ this._repeatID.consoleApiLevel = packet.level;
+ this._arguments = packet.arguments;
+};
+
+Messages.ConsoleTable.prototype = Heritage.extend(Messages.Extended.prototype,
+{
+ /**
+ * Holds the arguments the content script passed to the console.table()
+ * method.
+ *
+ * @private
+ * @type array
+ */
+ _arguments: null,
+
+ /**
+ * Array of objects that holds the data to log in the table.
+ *
+ * @private
+ * @type array
+ */
+ _data: null,
+
+ /**
+ * Key value pair of the id and display name for the columns in the table.
+ * Refer to the TableWidget API.
+ *
+ * @private
+ * @type object
+ */
+ _columns: null,
+
+ /**
+ * A promise that resolves when the table data is ready or null if invalid
+ * arguments are provided.
+ *
+ * @private
+ * @type promise|null
+ */
+ _populatePromise: null,
+
+ init: function()
+ {
+ let result = Messages.Extended.prototype.init.apply(this, arguments);
+ this._data = [];
+ this._columns = {};
+
+ this._populatePromise = this._populateTableData();
+
+ return result;
+ },
+
+ /**
+ * Sets the key value pair of the id and display name for the columns in the
+ * table.
+ *
+ * @private
+ * @param array|string columns
+ * Either a string or array containing the names for the columns in
+ * the output table.
+ */
+ _setColumns: function(columns)
+ {
+ if (columns.class == "Array") {
+ let items = columns.preview.items;
+
+ for (let item of items) {
+ if (typeof item == "string") {
+ this._columns[item] = item;
+ }
+ }
+ } else if (typeof columns == "string" && columns) {
+ this._columns[columns] = columns;
+ }
+ },
+
+ /**
+ * Retrieves the table data and columns from the arguments received from the
+ * server.
+ *
+ * @return Promise|null
+ * Returns a promise that resolves when the table data is ready or
+ * null if the arguments are invalid.
+ */
+ _populateTableData: function()
+ {
+ let deferred = promise.defer();
+
+ if (this._arguments.length <= 0) {
+ return;
+ }
+
+ let data = this._arguments[0];
+ if (data.class != "Array" && data.class != "Object" &&
+ data.class != "Map" && data.class != "Set") {
+ return;
+ }
+
+ let hasColumnsArg = false;
+ if (this._arguments.length > 1) {
+ if (data.class == "Object" || data.class == "Array") {
+ this._columns["_index"] = l10n.getStr("table.index");
+ } else {
+ this._columns["_index"] = l10n.getStr("table.iterationIndex");
+ }
+
+ this._setColumns(this._arguments[1]);
+ hasColumnsArg = true;
+ }
+
+ if (data.class == "Object" || data.class == "Array") {
+ // Get the object properties, and parse the key and value properties into
+ // the table data and columns.
+ this.client = new ObjectClient(this.output.owner.jsterm.hud.proxy.client,
+ data);
+ this.client.getPrototypeAndProperties(aResponse => {
+ let {ownProperties} = aResponse;
+ let rowCount = 0;
+ let columnCount = 0;
+
+ for (let index of Object.keys(ownProperties || {})) {
+ // Avoid outputting the length property if the data argument provided
+ // is an array
+ if (data.class == "Array" && index == "length") {
+ continue;
+ }
+
+ if (!hasColumnsArg) {
+ this._columns["_index"] = l10n.getStr("table.index");
+ }
+
+ if (data.class == "Array") {
+ if (index == parseInt(index)) {
+ index = parseInt(index);
+ }
+ }
+
+ let property = ownProperties[index].value;
+ let item = { _index: index };
+
+ if (property.class == "Object" || property.class == "Array") {
+ let {preview} = property;
+ let entries = property.class == "Object" ?
+ preview.ownProperties : preview.items;
+
+ for (let key of Object.keys(entries)) {
+ let value = property.class == "Object" ?
+ preview.ownProperties[key].value : preview.items[key];
+
+ item[key] = this._renderValueGrip(value, { concise: true });
+
+ if (!hasColumnsArg && !(key in this._columns) &&
+ (++columnCount <= TABLE_COLUMN_MAX_ITEMS)) {
+ this._columns[key] = key;
+ }
+ }
+ } else {
+ // Display the value for any non-object data input.
+ item["_value"] = this._renderValueGrip(property, { concise: true });
+
+ if (!hasColumnsArg && !("_value" in this._columns)) {
+ this._columns["_value"] = l10n.getStr("table.value");
+ }
+ }
+
+ this._data.push(item);
+
+ if (++rowCount == TABLE_ROW_MAX_ITEMS) {
+ break;
+ }
+ }
+
+ deferred.resolve();
+ });
+ } else if (data.class == "Map") {
+ let entries = data.preview.entries;
+
+ if (!hasColumnsArg) {
+ this._columns["_index"] = l10n.getStr("table.iterationIndex");
+ this._columns["_key"] = l10n.getStr("table.key");
+ this._columns["_value"] = l10n.getStr("table.value");
+ }
+
+ let rowCount = 0;
+ for (let [key, value] of entries) {
+ let item = {
+ _index: rowCount,
+ _key: this._renderValueGrip(key, { concise: true }),
+ _value: this._renderValueGrip(value, { concise: true })
+ };
+
+ this._data.push(item);
+
+ if (++rowCount == TABLE_ROW_MAX_ITEMS) {
+ break;
+ }
+ }
+
+ deferred.resolve();
+ } else if (data.class == "Set") {
+ let entries = data.preview.items;
+
+ if (!hasColumnsArg) {
+ this._columns["_index"] = l10n.getStr("table.iterationIndex");
+ this._columns["_value"] = l10n.getStr("table.value");
+ }
+
+ let rowCount = 0;
+ for (let entry of entries) {
+ let item = {
+ _index : rowCount,
+ _value: this._renderValueGrip(entry, { concise: true })
+ };
+
+ this._data.push(item);
+
+ if (++rowCount == TABLE_ROW_MAX_ITEMS) {
+ break;
+ }
+ }
+
+ deferred.resolve();
+ }
+
+ return deferred.promise;
+ },
+
+ render: function()
+ {
+ Messages.Extended.prototype.render.apply(this, arguments);
+ this.element.setAttribute("open", true);
+ return this;
+ },
+
+ /**
+ * Render the table.
+ *
+ * @private
+ * @return DOMElement
+ */
+ _renderTable: function()
+ {
+ let cmvar = this.document.createElementNS(XHTML_NS, "span");
+ cmvar.className = "cm-variable";
+ cmvar.textContent = "console";
+
+ let cmprop = this.document.createElementNS(XHTML_NS, "span");
+ cmprop.className = "cm-property";
+ cmprop.textContent = "table";
+
+ let title = this.document.createElementNS(XHTML_NS, "span");
+ title.className = "message-body devtools-monospace";
+ title.appendChild(cmvar);
+ title.appendChild(this.document.createTextNode("."));
+ title.appendChild(cmprop);
+ title.appendChild(this.document.createTextNode("():"));
+
+ let repeatNode = Messages.Simple.prototype._renderRepeatNode.call(this);
+ let location = Messages.Simple.prototype._renderLocation.call(this);
+ if (location) {
+ location.target = "jsdebugger";
+ }
+
+ let body = this.document.createElementNS(XHTML_NS, "span");
+ body.className = "message-flex-body";
+ body.appendChild(title);
+ if (repeatNode) {
+ body.appendChild(repeatNode);
+ }
+ if (location) {
+ body.appendChild(location);
+ }
+ body.appendChild(this.document.createTextNode("\n"));
+
+ let result = this.document.createElementNS(XHTML_NS, "div");
+ result.appendChild(body);
+
+ if (this._populatePromise) {
+ this._populatePromise.then(() => {
+ if (this._data.length > 0) {
+ let widget = new Widgets.Table(this, this._data, this._columns).render();
+ result.appendChild(widget.element);
+ }
+
+ result.scrollIntoView();
+ this.output.owner.emit("messages-table-rendered");
+
+ // Release object actors
+ if (Array.isArray(this._arguments)) {
+ for (let arg of this._arguments) {
+ if (WebConsoleUtils.isActorGrip(arg)) {
+ this.output._releaseObject(arg.actor);
+ }
+ }
+ }
+ this._arguments = null;
+ });
+ }
+
+ return result;
+ },
+
+ _renderBody: function()
+ {
+ let body = Messages.Simple.prototype._renderBody.apply(this, arguments);
+ body.classList.remove("devtools-monospace", "message-body");
+ return body;
+ },
+
+ // no-op for the message location and .repeats elements.
+ // |this._renderTable| handles customized message output.
+ _renderLocation: function() { },
+ _renderRepeatNode: function() { },
+}); // Messages.ConsoleTable.prototype
+
+let Widgets = {};
+
+/**
+ * The base widget class.
+ *
+ * @constructor
+ * @param object message
+ * The owning message.
+ */
+Widgets.BaseWidget = function(message)
+{
+ this.message = message;
+};
+
+Widgets.BaseWidget.prototype = {
+ /**
+ * The owning message object.
+ * @type object
+ */
+ message: null,
+
+ /**
+ * The DOM element of the rendered widget.
+ * @type Element
+ */
+ element: null,
+
+ /**
+ * Getter for the DOM document that holds the output.
+ * @type Document
+ */
+ get document() {
+ return this.message.document;
+ },
+
+ /**
+ * The ConsoleOutput instance that owns this widget instance.
+ */
+ get output() {
+ return this.message.output;
+ },
+
+ /**
+ * Render the widget DOM element.
+ * @return this
+ */
+ render: function() { },
+
+ /**
+ * Destroy this widget instance.
+ */
+ destroy: function() { },
+
+ /**
+ * Helper for creating DOM elements for widgets.
+ *
+ * Usage:
+ * this.el("tag#id.class.names"); // create element "tag" with ID "id" and
+ * two class names, .class and .names.
+ *
+ * this.el("span", { attr1: "value1", ... }) // second argument can be an
+ * object that holds element attributes and values for the new DOM element.
+ *
+ * this.el("p", { attr1: "value1", ... }, "text content"); // the third
+ * argument can include the default .textContent of the new DOM element.
+ *
+ * this.el("p", "text content"); // if the second argument is not an object,
+ * it will be used as .textContent for the new DOM element.
+ *
+ * @param string tagNameIdAndClasses
+ * Tag name for the new element, optionally followed by an ID and/or
+ * class names. Examples: "span", "div#fooId", "div.class.names",
+ * "p#id.class".
+ * @param string|object [attributesOrTextContent]
+ * If this argument is an object it will be used to set the attributes
+ * of the new DOM element. Otherwise, the value becomes the
+ * .textContent of the new DOM element.
+ * @param string [textContent]
+ * If this argument is provided the value is used as the textContent of
+ * the new DOM element.
+ * @return DOMElement
+ * The new DOM element.
+ */
+ el: function(tagNameIdAndClasses)
+ {
+ let attrs, text;
+ if (typeof arguments[1] == "object") {
+ attrs = arguments[1];
+ text = arguments[2];
+ } else {
+ text = arguments[1];
+ }
+
+ let tagName = tagNameIdAndClasses.split(/#|\./)[0];
+
+ let elem = this.document.createElementNS(XHTML_NS, tagName);
+ for (let name of Object.keys(attrs || {})) {
+ elem.setAttribute(name, attrs[name]);
+ }
+ if (text !== undefined && text !== null) {
+ elem.textContent = text;
+ }
+
+ let idAndClasses = tagNameIdAndClasses.match(/([#.][^#.]+)/g);
+ for (let idOrClass of (idAndClasses || [])) {
+ if (idOrClass.charAt(0) == "#") {
+ elem.id = idOrClass.substr(1);
+ } else {
+ elem.classList.add(idOrClass.substr(1));
+ }
+ }
+
+ return elem;
+ },
+};
+
+/**
+ * The timestamp widget.
+ *
+ * @constructor
+ * @param object message
+ * The owning message.
+ * @param number timestamp
+ * The UNIX timestamp to display.
+ */
+Widgets.MessageTimestamp = function(message, timestamp)
+{
+ Widgets.BaseWidget.call(this, message);
+ this.timestamp = timestamp;
+};
+
+Widgets.MessageTimestamp.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
+{
+ /**
+ * The UNIX timestamp.
+ * @type number
+ */
+ timestamp: 0,
+
+ render: function()
+ {
+ if (this.element) {
+ return this;
+ }
+
+ this.element = this.document.createElementNS(XHTML_NS, "span");
+ this.element.className = "timestamp devtools-monospace";
+ this.element.textContent = l10n.timestampString(this.timestamp) + " ";
+
+ return this;
+ },
+}); // Widgets.MessageTimestamp.prototype
+
+
+/**
+ * The URLString widget, for rendering strings where at least one token is a
+ * URL.
+ *
+ * @constructor
+ * @param object message
+ * The owning message.
+ * @param string str
+ * The string, which contains at least one valid URL.
+ */
+Widgets.URLString = function(message, str)
+{
+ Widgets.BaseWidget.call(this, message);
+ this.str = str;
+};
+
+Widgets.URLString.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
+{
+ /**
+ * The string to format, which contains at least one valid URL.
+ * @type string
+ */
+ str: "",
+
+ render: function()
+ {
+ if (this.element) {
+ return this;
+ }
+
+ // The rendered URLString will be a <span> containing a number of text
+ // <spans> for non-URL tokens and <a>'s for URL tokens.
+ this.element = this.el("span", {
+ class: "console-string"
+ });
+ this.element.appendChild(this._renderText("\""));
+
+ // As we walk through the tokens of the source string, we make sure to preserve
+ // the original whitespace that seperated the tokens.
+ let tokens = this.str.split(/\s+/);
+ let textStart = 0;
+ let tokenStart;
+ for (let token of tokens) {
+ tokenStart = this.str.indexOf(token, textStart);
+ if (this._isURL(token)) {
+ this.element.appendChild(this._renderText(this.str.slice(textStart, tokenStart)));
+ textStart = tokenStart + token.length;
+ this.element.appendChild(this._renderURL(token));
+ }
+ }
+
+ // Clean up any non-URL text at the end of the source string.
+ this.element.appendChild(this._renderText(this.str.slice(textStart, this.str.length)));
+ this.element.appendChild(this._renderText("\""));
+
+ return this;
+ },
+
+ /**
+ * Determines whether a grip is a string containing a URL.
+ *
+ * @param string grip
+ * The grip, which may contain a URL.
+ * @return boolean
+ * Whether the grip is a string containing a URL.
+ */
+ containsURL: function(grip)
+ {
+ if (typeof grip != "string") {
+ return false;
+ }
+
+ let tokens = grip.split(/\s+/);
+ return tokens.some(this._isURL);
+ },
+
+ /**
+ * Determines whether a string token is a valid URL.
+ *
+ * @param string token
+ * The token.
+ * @return boolean
+ * Whenther the token is a URL.
+ */
+ _isURL: function(token) {
+ try {
+ let uri = URI.newURI(token, null, null);
+ let url = uri.QueryInterface(Ci.nsIURL);
+ return true;
+ } catch (e) {
+ return false;
+ }
+ },
+
+ /**
+ * Renders a string as a URL.
+ *
+ * @param string url
+ * The string to be rendered as a url.
+ * @return DOMElement
+ * An element containing the rendered string.
+ */
+ _renderURL: function(url)
+ {
+ let result = this.el("a", {
+ class: "url",
+ title: url,
+ href: url,
+ draggable: false
+ }, url);
+ this.message._addLinkCallback(result);
+ return result;
+ },
+
+ _renderText: function(text) {
+ return this.el("span", text);
+ },
+}); // Widgets.URLString.prototype
+
+/**
+ * Widget used for displaying ObjectActors that have no specialised renderers.
+ *
+ * @constructor
+ * @param object message
+ * The owning message.
+ * @param object objectActor
+ * The ObjectActor to display.
+ * @param object [options]
+ * Options for displaying the given ObjectActor. See
+ * Messages.Extended.prototype._renderValueGrip for the available
+ * options.
+ */
+Widgets.JSObject = function(message, objectActor, options = {})
+{
+ Widgets.BaseWidget.call(this, message);
+ this.objectActor = objectActor;
+ this.options = options;
+ this._onClick = this._onClick.bind(this);
+};
+
+Widgets.JSObject.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
+{
+ /**
+ * The ObjectActor displayed by the widget.
+ * @type object
+ */
+ objectActor: null,
+
+ render: function()
+ {
+ if (!this.element) {
+ this._render();
+ }
+
+ return this;
+ },
+
+ _render: function()
+ {
+ let str = VariablesView.getString(this.objectActor, this.options);
+ let className = this.message.getClassNameForValueGrip(this.objectActor);
+ if (!className && this.objectActor.class == "Object") {
+ className = "cm-variable";
+ }
+
+ this.element = this._anchor(str, { className: className });
+ },
+
+ /**
+ * Render a concise representation of an object.
+ */
+ _renderConciseObject: function()
+ {
+ this.element = this._anchor(this.objectActor.class,
+ { className: "cm-variable" });
+ },
+
+ /**
+ * Render the `<class> { ` prefix of an object.
+ */
+ _renderObjectPrefix: function()
+ {
+ let { kind } = this.objectActor.preview;
+ this.element = this.el("span.kind-" + kind);
+ this._anchor(this.objectActor.class, { className: "cm-variable" });
+ this._text(" { ");
+ },
+
+ /**
+ * Render the ` }` suffix of an object.
+ */
+ _renderObjectSuffix: function()
+ {
+ this._text(" }");
+ },
+
+ /**
+ * Render an object property.
+ *
+ * @param String key
+ * The property name.
+ * @param Object value
+ * The property value, as an RDP grip.
+ * @param nsIDOMNode container
+ * The container node to render to.
+ * @param Boolean needsComma
+ * True if there was another property before this one and we need to
+ * separate them with a comma.
+ * @param Boolean valueIsText
+ * Add the value as is, don't treat it as a grip and pass it to
+ * `_renderValueGrip`.
+ */
+ _renderObjectProperty: function(key, value, container, needsComma, valueIsText = false)
+ {
+ if (needsComma) {
+ this._text(", ");
+ }
+
+ container.appendChild(this.el("span.cm-property", key));
+ this._text(": ");
+
+ if (valueIsText) {
+ this._text(value);
+ } else {
+ let shortVal = this.message.shortenValueGrip(value);
+ let valueElem = this.message._renderValueGrip(shortVal, { concise: true });
+ container.appendChild(valueElem);
+ }
+ },
+
+ /**
+ * Render this object's properties.
+ *
+ * @param nsIDOMNode container
+ * The container node to render to.
+ * @param Boolean needsComma
+ * True if there was another property before this one and we need to
+ * separate them with a comma.
+ */
+ _renderObjectProperties: function(container, needsComma)
+ {
+ let { preview } = this.objectActor;
+ let { ownProperties, safeGetterValues } = preview;
+
+ let shown = 0;
+
+ let getValue = desc => {
+ if (desc.get) {
+ return "Getter";
+ } else if (desc.set) {
+ return "Setter";
+ } else {
+ return desc.value;
+ }
+ };
+
+ for (let key of Object.keys(ownProperties || {})) {
+ this._renderObjectProperty(key, getValue(ownProperties[key]), container,
+ shown > 0 || needsComma,
+ ownProperties[key].get || ownProperties[key].set);
+ shown++;
+ }
+
+ let ownPropertiesShown = shown;
+
+ for (let key of Object.keys(safeGetterValues || {})) {
+ this._renderObjectProperty(key, safeGetterValues[key].getterValue,
+ container, shown > 0 || needsComma);
+ shown++;
+ }
+
+ if (typeof preview.ownPropertiesLength == "number" &&
+ ownPropertiesShown < preview.ownPropertiesLength) {
+ this._text(", ");
+
+ let n = preview.ownPropertiesLength - ownPropertiesShown;
+ let str = VariablesView.stringifiers._getNMoreString(n);
+ this._anchor(str);
+ }
+ },
+
+ /**
+ * Render an anchor with a given text content and link.
+ *
+ * @private
+ * @param string text
+ * Text to show in the anchor.
+ * @param object [options]
+ * Available options:
+ * - onClick (function): "click" event handler.By default a click on
+ * the anchor opens the variables view for the current object actor
+ * (this.objectActor).
+ * - href (string): if given the string is used as a link, and clicks
+ * on the anchor open the link in a new tab.
+ * - appendTo (DOMElement): append the element to the given DOM
+ * element. If not provided, the anchor is appended to |this.element|
+ * if it is available. If |appendTo| is provided and if it is a falsy
+ * value, the anchor is not appended to any element.
+ * @return DOMElement
+ * The DOM element of the new anchor.
+ */
+ _anchor: function(text, options = {})
+ {
+ if (!options.onClick) {
+ // If the anchor has an URL, open it in a new tab. If not, show the
+ // current object actor.
+ options.onClick = options.href ? this._onClickAnchor : this._onClick;
+ }
+
+ let anchor = this.el("a", {
+ class: options.className,
+ draggable: false,
+ href: options.href || "#",
+ }, text);
+
+ this.message._addLinkCallback(anchor, options.onClick);
+
+ if (options.appendTo) {
+ options.appendTo.appendChild(anchor);
+ } else if (!("appendTo" in options) && this.element) {
+ this.element.appendChild(anchor);
+ }
+
+ return anchor;
+ },
+
+ /**
+ * The click event handler for objects shown inline.
+ * @private
+ */
+ _onClick: function()
+ {
+ this.output.openVariablesView({
+ label: VariablesView.getString(this.objectActor, { concise: true }),
+ objectActor: this.objectActor,
+ autofocus: true,
+ });
+ },
+
+ /**
+ * Add a string to the message.
+ *
+ * @private
+ * @param string str
+ * String to add.
+ * @param DOMElement [target = this.element]
+ * Optional DOM element to append the string to. The default is
+ * this.element.
+ */
+ _text: function(str, target = this.element)
+ {
+ target.appendChild(this.document.createTextNode(str));
+ },
+}); // Widgets.JSObject.prototype
+
+Widgets.ObjectRenderers = {};
+Widgets.ObjectRenderers.byKind = {};
+Widgets.ObjectRenderers.byClass = {};
+
+/**
+ * Add an object renderer.
+ *
+ * @param object obj
+ * An object that represents the renderer. Properties:
+ * - byClass (string, optional): this renderer will be used for the given
+ * object class.
+ * - byKind (string, optional): this renderer will be used for the given
+ * object kind.
+ * One of byClass or byKind must be provided.
+ * - extends (object, optional): the renderer object extends the given
+ * object. Default: Widgets.JSObject.
+ * - canRender (function, optional): this method is invoked when
+ * a candidate object needs to be displayed. The method is invoked as
+ * a static method, as such, none of the properties of the renderer
+ * object will be available. You get one argument: the object actor grip
+ * received from the server. If the method returns true, then this
+ * renderer is used for displaying the object, otherwise not.
+ * - initialize (function, optional): the constructor of the renderer
+ * widget. This function is invoked with the following arguments: the
+ * owner message object instance, the object actor grip to display, and
+ * an options object. See Messages.Extended.prototype._renderValueGrip()
+ * for details about the options object.
+ * - render (function, required): the method that displays the given
+ * object actor.
+ */
+Widgets.ObjectRenderers.add = function(obj)
+{
+ let extendObj = obj.extends || Widgets.JSObject;
+
+ let constructor = function() {
+ if (obj.initialize) {
+ obj.initialize.apply(this, arguments);
+ } else {
+ extendObj.apply(this, arguments);
+ }
+ };
+
+ let proto = WebConsoleUtils.cloneObject(obj, false, function(key) {
+ if (key == "initialize" || key == "canRender" ||
+ (key == "render" && extendObj === Widgets.JSObject)) {
+ return false;
+ }
+ return true;
+ });
+
+ if (extendObj === Widgets.JSObject) {
+ proto._render = obj.render;
+ }
+
+ constructor.canRender = obj.canRender;
+ constructor.prototype = Heritage.extend(extendObj.prototype, proto);
+
+ if (obj.byClass) {
+ Widgets.ObjectRenderers.byClass[obj.byClass] = constructor;
+ } else if (obj.byKind) {
+ Widgets.ObjectRenderers.byKind[obj.byKind] = constructor;
+ } else {
+ throw new Error("You are adding an object renderer without any byClass or " +
+ "byKind property.");
+ }
+};
+
+
+/**
+ * The widget used for displaying Date objects.
+ */
+Widgets.ObjectRenderers.add({
+ byClass: "Date",
+
+ render: function()
+ {
+ let {preview} = this.objectActor;
+ this.element = this.el("span.class-" + this.objectActor.class);
+
+ let anchorText = this.objectActor.class;
+ let anchorClass = "cm-variable";
+ if (preview && "timestamp" in preview && typeof preview.timestamp != "number") {
+ anchorText = new Date(preview.timestamp).toString(); // invalid date
+ anchorClass = "";
+ }
+
+ this._anchor(anchorText, { className: anchorClass });
+
+ if (!preview || !("timestamp" in preview) || typeof preview.timestamp != "number") {
+ return;
+ }
+
+ this._text(" ");
+
+ let elem = this.el("span.cm-string-2", new Date(preview.timestamp).toISOString());
+ this.element.appendChild(elem);
+ },
+});
+
+/**
+ * The widget used for displaying Function objects.
+ */
+Widgets.ObjectRenderers.add({
+ byClass: "Function",
+
+ render: function()
+ {
+ let grip = this.objectActor;
+ this.element = this.el("span.class-" + this.objectActor.class);
+
+ // TODO: Bug 948484 - support arrow functions and ES6 generators
+ let name = grip.userDisplayName || grip.displayName || grip.name || "";
+ name = VariablesView.getString(name, { noStringQuotes: true });
+
+ let str = this.options.concise ? name || "function " : "function " + name;
+
+ if (this.options.concise) {
+ this._anchor(name || "function", {
+ className: name ? "cm-variable" : "cm-keyword",
+ });
+ if (!name) {
+ this._text(" ");
+ }
+ } else if (name) {
+ this.element.appendChild(this.el("span.cm-keyword", "function"));
+ this._text(" ");
+ this._anchor(name, { className: "cm-variable" });
+ } else {
+ this._anchor("function", { className: "cm-keyword" });
+ this._text(" ");
+ }
+
+ this._text("(");
+
+ // TODO: Bug 948489 - Support functions with destructured parameters and
+ // rest parameters
+ let params = grip.parameterNames || [];
+ let shown = 0;
+ for (let param of params) {
+ if (shown > 0) {
+ this._text(", ");
+ }
+ this.element.appendChild(this.el("span.cm-def", param));
+ shown++;
+ }
+
+ this._text(")");
+ },
+}); // Widgets.ObjectRenderers.byClass.Function
+
+/**
+ * The widget used for displaying ArrayLike objects.
+ */
+Widgets.ObjectRenderers.add({
+ byKind: "ArrayLike",
+
+ render: function()
+ {
+ let {preview} = this.objectActor;
+ let {items} = preview;
+ this.element = this.el("span.kind-" + preview.kind);
+
+ this._anchor(this.objectActor.class, { className: "cm-variable" });
+
+ if (!items || this.options.concise) {
+ this._text("[");
+ this.element.appendChild(this.el("span.cm-number", preview.length));
+ this._text("]");
+ return this;
+ }
+
+ this._text(" [ ");
+
+ let isFirst = true;
+ let emptySlots = 0;
+ // A helper that renders a comma between items if isFirst == false.
+ let renderSeparator = () => !isFirst && this._text(", ");
+
+ for (let item of items) {
+ if (item === null) {
+ emptySlots++;
+ }
+ else {
+ renderSeparator();
+ isFirst = false;
+
+ if (emptySlots) {
+ this._renderEmptySlots(emptySlots);
+ emptySlots = 0;
+ }
+
+ let shortVal = this.message.shortenValueGrip(item);
+ let elem = this.message._renderValueGrip(shortVal, { concise: true });
+ this.element.appendChild(elem);
+ }
+ }
+
+ if (emptySlots) {
+ renderSeparator();
+ this._renderEmptySlots(emptySlots, false);
+ }
+
+ let shown = items.length;
+ if (shown < preview.length) {
+ this._text(", ");
+
+ let n = preview.length - shown;
+ let str = VariablesView.stringifiers._getNMoreString(n);
+ this._anchor(str);
+ }
+
+ this._text(" ]");
+ },
+
+ _renderEmptySlots: function(aNumSlots, aAppendComma=true) {
+ let slotLabel = l10n.getStr("emptySlotLabel");
+ let slotText = PluralForm.get(aNumSlots, slotLabel);
+ this._text("<" + slotText.replace("#1", aNumSlots) + ">");
+ if (aAppendComma) {
+ this._text(", ");
+ }
+ },
+
+}); // Widgets.ObjectRenderers.byKind.ArrayLike
+
+/**
+ * The widget used for displaying MapLike objects.
+ */
+Widgets.ObjectRenderers.add({
+ byKind: "MapLike",
+
+ render: function()
+ {
+ let {preview} = this.objectActor;
+ let {entries} = preview;
+
+ let container = this.element = this.el("span.kind-" + preview.kind);
+ this._anchor(this.objectActor.class, { className: "cm-variable" });
+
+ if (!entries || this.options.concise) {
+ if (typeof preview.size == "number") {
+ this._text("[");
+ container.appendChild(this.el("span.cm-number", preview.size));
+ this._text("]");
+ }
+ return;
+ }
+
+ this._text(" { ");
+
+ let shown = 0;
+ for (let [key, value] of entries) {
+ if (shown > 0) {
+ this._text(", ");
+ }
+
+ let keyElem = this.message._renderValueGrip(key, {
+ concise: true,
+ noStringQuotes: true,
+ });
+
+ // Strings are property names.
+ if (keyElem.classList && keyElem.classList.contains("console-string")) {
+ keyElem.classList.remove("console-string");
+ keyElem.classList.add("cm-property");
+ }
+
+ container.appendChild(keyElem);
+
+ this._text(": ");
+
+ let valueElem = this.message._renderValueGrip(value, { concise: true });
+ container.appendChild(valueElem);
+
+ shown++;
+ }
+
+ if (typeof preview.size == "number" && shown < preview.size) {
+ this._text(", ");
+
+ let n = preview.size - shown;
+ let str = VariablesView.stringifiers._getNMoreString(n);
+ this._anchor(str);
+ }
+
+ this._text(" }");
+ },
+}); // Widgets.ObjectRenderers.byKind.MapLike
+
+/**
+ * The widget used for displaying objects with a URL.
+ */
+Widgets.ObjectRenderers.add({
+ byKind: "ObjectWithURL",
+
+ render: function()
+ {
+ this.element = this._renderElement(this.objectActor,
+ this.objectActor.preview.url);
+ },
+
+ _renderElement: function(objectActor, url)
+ {
+ let container = this.el("span.kind-" + objectActor.preview.kind);
+
+ this._anchor(objectActor.class, {
+ className: "cm-variable",
+ appendTo: container,
+ });
+
+ if (!VariablesView.isFalsy({ value: url })) {
+ this._text(" \u2192 ", container);
+ let shortUrl = WebConsoleUtils.abbreviateSourceURL(url, {
+ onlyCropQuery: !this.options.concise
+ });
+ this._anchor(shortUrl, { href: url, appendTo: container });
+ }
+
+ return container;
+ },
+}); // Widgets.ObjectRenderers.byKind.ObjectWithURL
+
+/**
+ * The widget used for displaying objects with a string next to them.
+ */
+Widgets.ObjectRenderers.add({
+ byKind: "ObjectWithText",
+
+ render: function()
+ {
+ let {preview} = this.objectActor;
+ this.element = this.el("span.kind-" + preview.kind);
+
+ this._anchor(this.objectActor.class, { className: "cm-variable" });
+
+ if (!this.options.concise) {
+ this._text(" ");
+ this.element.appendChild(this.el("span.console-string",
+ VariablesView.getString(preview.text)));
+ }
+ },
+});
+
+/**
+ * The widget used for displaying DOM event previews.
+ */
+Widgets.ObjectRenderers.add({
+ byKind: "DOMEvent",
+
+ render: function()
+ {
+ let {preview} = this.objectActor;
+
+ let container = this.element = this.el("span.kind-" + preview.kind);
+
+ this._anchor(preview.type || this.objectActor.class,
+ { className: "cm-variable" });
+
+ if (this.options.concise) {
+ return;
+ }
+
+ if (preview.eventKind == "key" && preview.modifiers &&
+ preview.modifiers.length) {
+ this._text(" ");
+
+ let mods = 0;
+ for (let mod of preview.modifiers) {
+ if (mods > 0) {
+ this._text("-");
+ }
+ container.appendChild(this.el("span.cm-keyword", mod));
+ mods++;
+ }
+ }
+
+ this._text(" { ");
+
+ let shown = 0;
+ if (preview.target) {
+ container.appendChild(this.el("span.cm-property", "target"));
+ this._text(": ");
+ let target = this.message._renderValueGrip(preview.target, { concise: true });
+ container.appendChild(target);
+ shown++;
+ }
+
+ for (let key of Object.keys(preview.properties || {})) {
+ if (shown > 0) {
+ this._text(", ");
+ }
+
+ container.appendChild(this.el("span.cm-property", key));
+ this._text(": ");
+
+ let value = preview.properties[key];
+ let valueElem = this.message._renderValueGrip(value, { concise: true });
+ container.appendChild(valueElem);
+
+ shown++;
+ }
+
+ this._text(" }");
+ },
+}); // Widgets.ObjectRenderers.byKind.DOMEvent
+
+/**
+ * The widget used for displaying DOM node previews.
+ */
+Widgets.ObjectRenderers.add({
+ byKind: "DOMNode",
+
+ canRender: function(objectActor) {
+ let {preview} = objectActor;
+ if (!preview) {
+ return false;
+ }
+
+ switch (preview.nodeType) {
+ case Ci.nsIDOMNode.DOCUMENT_NODE:
+ case Ci.nsIDOMNode.ATTRIBUTE_NODE:
+ case Ci.nsIDOMNode.TEXT_NODE:
+ case Ci.nsIDOMNode.COMMENT_NODE:
+ case Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE:
+ case Ci.nsIDOMNode.ELEMENT_NODE:
+ return true;
+ default:
+ return false;
+ }
+ },
+
+ render: function()
+ {
+ switch (this.objectActor.preview.nodeType) {
+ case Ci.nsIDOMNode.DOCUMENT_NODE:
+ this._renderDocumentNode();
+ break;
+ case Ci.nsIDOMNode.ATTRIBUTE_NODE: {
+ let {preview} = this.objectActor;
+ this.element = this.el("span.attributeNode.kind-" + preview.kind);
+ let attr = this._renderAttributeNode(preview.nodeName, preview.value, true);
+ this.element.appendChild(attr);
+ break;
+ }
+ case Ci.nsIDOMNode.TEXT_NODE:
+ this._renderTextNode();
+ break;
+ case Ci.nsIDOMNode.COMMENT_NODE:
+ this._renderCommentNode();
+ break;
+ case Ci.nsIDOMNode.DOCUMENT_FRAGMENT_NODE:
+ this._renderDocumentFragmentNode();
+ break;
+ case Ci.nsIDOMNode.ELEMENT_NODE:
+ this._renderElementNode();
+ break;
+ default:
+ throw new Error("Unsupported nodeType: " + preview.nodeType);
+ }
+ },
+
+ _renderDocumentNode: function()
+ {
+ let fn = Widgets.ObjectRenderers.byKind.ObjectWithURL.prototype._renderElement;
+ this.element = fn.call(this, this.objectActor,
+ this.objectActor.preview.location);
+ this.element.classList.add("documentNode");
+ },
+
+ _renderAttributeNode: function(nodeName, nodeValue, addLink)
+ {
+ let value = VariablesView.getString(nodeValue, { noStringQuotes: true });
+
+ let fragment = this.document.createDocumentFragment();
+ if (addLink) {
+ this._anchor(nodeName, { className: "cm-attribute", appendTo: fragment });
+ } else {
+ fragment.appendChild(this.el("span.cm-attribute", nodeName));
+ }
+
+ this._text("=", fragment);
+ fragment.appendChild(this.el("span.console-string",
+ '"' + escapeHTML(value) + '"'));
+
+ return fragment;
+ },
+
+ _renderTextNode: function()
+ {
+ let {preview} = this.objectActor;
+ this.element = this.el("span.textNode.kind-" + preview.kind);
+
+ this._anchor(preview.nodeName, { className: "cm-variable" });
+ this._text(" ");
+
+ let text = VariablesView.getString(preview.textContent);
+ this.element.appendChild(this.el("span.console-string", text));
+ },
+
+ _renderCommentNode: function()
+ {
+ let {preview} = this.objectActor;
+ let comment = "<!-- " + VariablesView.getString(preview.textContent, {
+ noStringQuotes: true,
+ }) + " -->";
+
+ this.element = this._anchor(comment, {
+ className: "kind-" + preview.kind + " commentNode cm-comment",
+ });
+ },
+
+ _renderDocumentFragmentNode: function()
+ {
+ let {preview} = this.objectActor;
+ let {childNodes} = preview;
+ let container = this.element = this.el("span.documentFragmentNode.kind-" +
+ preview.kind);
+
+ this._anchor(this.objectActor.class, { className: "cm-variable" });
+
+ if (!childNodes || this.options.concise) {
+ this._text("[");
+ container.appendChild(this.el("span.cm-number", preview.childNodesLength));
+ this._text("]");
+ return;
+ }
+
+ this._text(" [ ");
+
+ let shown = 0;
+ for (let item of childNodes) {
+ if (shown > 0) {
+ this._text(", ");
+ }
+
+ let elem = this.message._renderValueGrip(item, { concise: true });
+ container.appendChild(elem);
+ shown++;
+ }
+
+ if (shown < preview.childNodesLength) {
+ this._text(", ");
+
+ let n = preview.childNodesLength - shown;
+ let str = VariablesView.stringifiers._getNMoreString(n);
+ this._anchor(str);
+ }
+
+ this._text(" ]");
+ },
+
+ _renderElementNode: function()
+ {
+ let doc = this.document;
+ let {attributes, nodeName} = this.objectActor.preview;
+
+ this.element = this.el("span." + "kind-" + this.objectActor.preview.kind + ".elementNode");
+
+ let openTag = this.el("span.cm-tag");
+ openTag.textContent = "<";
+ this.element.appendChild(openTag);
+
+ let tagName = this._anchor(nodeName, {
+ className: "cm-tag",
+ appendTo: openTag
+ });
+
+ if (this.options.concise) {
+ if (attributes.id) {
+ tagName.appendChild(this.el("span.cm-attribute", "#" + attributes.id));
+ }
+ if (attributes.class) {
+ tagName.appendChild(this.el("span.cm-attribute", "." + attributes.class.split(/\s+/g).join(".")));
+ }
+ } else {
+ for (let name of Object.keys(attributes)) {
+ let attr = this._renderAttributeNode(" " + name, attributes[name]);
+ this.element.appendChild(attr);
+ }
+ }
+
+ let closeTag = this.el("span.cm-tag");
+ closeTag.textContent = ">";
+ this.element.appendChild(closeTag);
+
+ // Register this widget in the owner message so that it gets destroyed when
+ // the message is destroyed.
+ this.message.widgets.add(this);
+
+ this.linkToInspector().then(null, Cu.reportError);
+ },
+
+ /**
+ * If the DOMNode being rendered can be highlit in the page, this function
+ * will attach mouseover/out event listeners to do so, and the inspector icon
+ * to open the node in the inspector.
+ * @return a promise that resolves when the node has been linked to the
+ * inspector, or rejects if it wasn't (either if no toolbox could be found to
+ * access the inspector, or if the node isn't present in the inspector, i.e.
+ * if the node is in a DocumentFragment or not part of the tree, or not of
+ * type Ci.nsIDOMNode.ELEMENT_NODE).
+ */
+ linkToInspector: Task.async(function*()
+ {
+ if (this._linkedToInspector) {
+ return;
+ }
+
+ // Checking the node type
+ if (this.objectActor.preview.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) {
+ throw new Error("The object cannot be linked to the inspector as it " +
+ "isn't an element node");
+ }
+
+ // Checking the presence of a toolbox
+ let target = this.message.output.toolboxTarget;
+ this.toolbox = gDevTools.getToolbox(target);
+ if (!this.toolbox) {
+ throw new Error("The object cannot be linked to the inspector without a " +
+ "toolbox");
+ }
+
+ // Checking that the inspector supports the node
+ yield this.toolbox.initInspector();
+ this._nodeFront = yield this.toolbox.walker.getNodeActorFromObjectActor(this.objectActor.actor);
+ if (!this._nodeFront) {
+ throw new Error("The object cannot be linked to the inspector, the " +
+ "corresponding nodeFront could not be found");
+ }
+
+ // At this stage, the message may have been cleared already
+ if (!this.document) {
+ throw new Error("The object cannot be linked to the inspector, the " +
+ "message was got cleared away");
+ }
+
+ this.highlightDomNode = this.highlightDomNode.bind(this);
+ this.element.addEventListener("mouseover", this.highlightDomNode, false);
+ this.unhighlightDomNode = this.unhighlightDomNode.bind(this);
+ this.element.addEventListener("mouseout", this.unhighlightDomNode, false);
+
+ this._openInspectorNode = this._anchor("", {
+ className: "open-inspector",
+ onClick: this.openNodeInInspector.bind(this)
+ });
+ this._openInspectorNode.title = l10n.getStr("openNodeInInspector");
+
+ this._linkedToInspector = true;
+ }),
+
+ /**
+ * Highlight the DOMNode corresponding to the ObjectActor in the page.
+ * @return a promise that resolves when the node has been highlighted, or
+ * rejects if the node cannot be highlighted (detached from the DOM)
+ */
+ highlightDomNode: Task.async(function*()
+ {
+ yield this.linkToInspector();
+ let isAttached = yield this.toolbox.walker.isInDOMTree(this._nodeFront);
+ if (isAttached) {
+ yield this.toolbox.highlighterUtils.highlightNodeFront(this._nodeFront);
+ } else {
+ throw null;
+ }
+ }),
+
+ /**
+ * Unhighlight a previously highlit node
+ * @see highlightDomNode
+ * @return a promise that resolves when the highlighter has been hidden
+ */
+ unhighlightDomNode: function()
+ {
+ return this.linkToInspector().then(() => {
+ return this.toolbox.highlighterUtils.unhighlight();
+ }).then(null, Cu.reportError);
+ },
+
+ /**
+ * Open the DOMNode corresponding to the ObjectActor in the inspector panel
+ * @return a promise that resolves when the inspector has been switched to
+ * and the node has been selected, or rejects if the node cannot be selected
+ * (detached from the DOM). Note that in any case, the inspector panel will
+ * be switched to.
+ */
+ openNodeInInspector: Task.async(function*()
+ {
+ yield this.linkToInspector();
+ yield this.toolbox.selectTool("inspector");
+
+ let isAttached = yield this.toolbox.walker.isInDOMTree(this._nodeFront);
+ if (isAttached) {
+ let onReady = this.toolbox.inspector.once("inspector-updated");
+ yield this.toolbox.selection.setNodeFront(this._nodeFront, "console");
+ yield onReady;
+ } else {
+ throw null;
+ }
+ }),
+
+ destroy: function()
+ {
+ if (this.toolbox && this._nodeFront) {
+ this.element.removeEventListener("mouseover", this.highlightDomNode, false);
+ this.element.removeEventListener("mouseout", this.unhighlightDomNode, false);
+ this._openInspectorNode.removeEventListener("mousedown", this.openNodeInInspector, true);
+ this.toolbox = null;
+ this._nodeFront = null;
+ }
+ },
+}); // Widgets.ObjectRenderers.byKind.DOMNode
+
+/**
+ * The widget user for displaying Promise objects.
+ */
+Widgets.ObjectRenderers.add({
+ byClass: "Promise",
+
+ render: function()
+ {
+ let { ownProperties, safeGetterValues } = this.objectActor.preview;
+ if ((!ownProperties && !safeGetterValues) || this.options.concise) {
+ this._renderConciseObject();
+ return;
+ }
+
+ this._renderObjectPrefix();
+ let container = this.element;
+ let addedPromiseInternalProps = false;
+
+ if (this.objectActor.promiseState) {
+ const { state, value, reason } = this.objectActor.promiseState;
+
+ this._renderObjectProperty("<state>", state, container, false);
+ addedPromiseInternalProps = true;
+
+ if (state == "fulfilled") {
+ this._renderObjectProperty("<value>", value, container, true);
+ } else if (state == "rejected") {
+ this._renderObjectProperty("<reason>", reason, container, true);
+ }
+ }
+
+ this._renderObjectProperties(container, addedPromiseInternalProps);
+ this._renderObjectSuffix();
+ }
+}); // Widgets.ObjectRenderers.byClass.Promise
+
+/**
+ * The widget used for displaying generic JS object previews.
+ */
+Widgets.ObjectRenderers.add({
+ byKind: "Object",
+
+ render: function()
+ {
+ let { ownProperties, safeGetterValues } = this.objectActor.preview;
+ if ((!ownProperties && !safeGetterValues) || this.options.concise) {
+ this._renderConciseObject();
+ return;
+ }
+
+ this._renderObjectPrefix();
+ this._renderObjectProperties(this.element, false);
+ this._renderObjectSuffix();
+ },
+}); // Widgets.ObjectRenderers.byKind.Object
+
+/**
+ * The long string widget.
+ *
+ * @constructor
+ * @param object message
+ * The owning message.
+ * @param object longStringActor
+ * The LongStringActor to display.
+ */
+Widgets.LongString = function(message, longStringActor)
+{
+ Widgets.BaseWidget.call(this, message);
+ this.longStringActor = longStringActor;
+ this._onClick = this._onClick.bind(this);
+ this._onSubstring = this._onSubstring.bind(this);
+};
+
+Widgets.LongString.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
+{
+ /**
+ * The LongStringActor displayed by the widget.
+ * @type object
+ */
+ longStringActor: null,
+
+ render: function()
+ {
+ if (this.element) {
+ return this;
+ }
+
+ let result = this.element = this.document.createElementNS(XHTML_NS, "span");
+ result.className = "longString console-string";
+ this._renderString(this.longStringActor.initial);
+ result.appendChild(this._renderEllipsis());
+
+ return this;
+ },
+
+ /**
+ * Render the long string in the widget element.
+ * @private
+ * @param string str
+ * The string to display.
+ */
+ _renderString: function(str)
+ {
+ this.element.textContent = VariablesView.getString(str, {
+ noStringQuotes: !this.message._quoteStrings,
+ noEllipsis: true,
+ });
+ },
+
+ /**
+ * Render the anchor ellipsis that allows the user to expand the long string.
+ *
+ * @private
+ * @return Element
+ */
+ _renderEllipsis: function()
+ {
+ let ellipsis = this.document.createElementNS(XHTML_NS, "a");
+ ellipsis.className = "longStringEllipsis";
+ ellipsis.textContent = l10n.getStr("longStringEllipsis");
+ ellipsis.href = "#";
+ ellipsis.draggable = false;
+ this.message._addLinkCallback(ellipsis, this._onClick);
+
+ return ellipsis;
+ },
+
+ /**
+ * The click event handler for the ellipsis shown after the short string. This
+ * function expands the element to show the full string.
+ * @private
+ */
+ _onClick: function()
+ {
+ let longString = this.output.webConsoleClient.longString(this.longStringActor);
+ let toIndex = Math.min(longString.length, MAX_LONG_STRING_LENGTH);
+
+ longString.substring(longString.initial.length, toIndex, this._onSubstring);
+ },
+
+ /**
+ * The longString substring response callback.
+ *
+ * @private
+ * @param object response
+ * Response packet.
+ */
+ _onSubstring: function(response)
+ {
+ if (response.error) {
+ Cu.reportError("LongString substring failure: " + response.error);
+ return;
+ }
+
+ this.element.lastChild.remove();
+ this.element.classList.remove("longString");
+
+ this._renderString(this.longStringActor.initial + response.substring);
+
+ this.output.owner.emit("new-messages", new Set([{
+ update: true,
+ node: this.message.element,
+ response: response,
+ }]));
+
+ let toIndex = Math.min(this.longStringActor.length, MAX_LONG_STRING_LENGTH);
+ if (toIndex != this.longStringActor.length) {
+ this._logWarningAboutStringTooLong();
+ }
+ },
+
+ /**
+ * Inform user that the string he tries to view is too long.
+ * @private
+ */
+ _logWarningAboutStringTooLong: function()
+ {
+ let msg = new Messages.Simple(l10n.getStr("longStringTooLong"), {
+ category: "output",
+ severity: "warning",
+ });
+ this.output.addMessage(msg);
+ },
+}); // Widgets.LongString.prototype
+
+
+/**
+ * The stacktrace widget.
+ *
+ * @constructor
+ * @extends Widgets.BaseWidget
+ * @param object message
+ * The owning message.
+ * @param array stacktrace
+ * The stacktrace to display, array of frames as supplied by the server,
+ * over the remote protocol.
+ */
+Widgets.Stacktrace = function(message, stacktrace)
+{
+ Widgets.BaseWidget.call(this, message);
+ this.stacktrace = stacktrace;
+};
+
+Widgets.Stacktrace.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
+{
+ /**
+ * The stackframes received from the server.
+ * @type array
+ */
+ stacktrace: null,
+
+ render: function()
+ {
+ if (this.element) {
+ return this;
+ }
+
+ let result = this.element = this.document.createElementNS(XHTML_NS, "ul");
+ result.className = "stacktrace devtools-monospace";
+
+ for (let frame of this.stacktrace) {
+ result.appendChild(this._renderFrame(frame));
+ }
+
+ return this;
+ },
+
+ /**
+ * Render a frame object received from the server.
+ *
+ * @param object frame
+ * The stack frame to display. This object should have the following
+ * properties: functionName, filename and lineNumber.
+ * @return DOMElement
+ * The DOM element to display for the given frame.
+ */
+ _renderFrame: function(frame)
+ {
+ let fn = this.document.createElementNS(XHTML_NS, "span");
+ fn.className = "function";
+ if (frame.functionName) {
+ let span = this.document.createElementNS(XHTML_NS, "span");
+ span.className = "cm-variable";
+ span.textContent = frame.functionName;
+ fn.appendChild(span);
+ fn.appendChild(this.document.createTextNode("()"));
+ } else {
+ fn.classList.add("cm-comment");
+ fn.textContent = l10n.getStr("stacktrace.anonymousFunction");
+ }
+
+ let location = this.output.owner.createLocationNode({url: frame.filename,
+ line: frame.lineNumber},
+ "jsdebugger");
+
+ // .devtools-monospace sets font-size to 80%, however .body already has
+ // .devtools-monospace. If we keep it here, the location would be rendered
+ // smaller.
+ location.classList.remove("devtools-monospace");
+
+ let elem = this.document.createElementNS(XHTML_NS, "li");
+ elem.appendChild(fn);
+ elem.appendChild(location);
+ elem.appendChild(this.document.createTextNode("\n"));
+
+ return elem;
+ },
+}); // Widgets.Stacktrace.prototype
+
+
+/**
+ * The table widget.
+ *
+ * @constructor
+ * @extends Widgets.BaseWidget
+ * @param object message
+ * The owning message.
+ * @param array data
+ * Array of objects that holds the data to log in the table.
+ * @param object columns
+ * Object containing the key value pair of the id and display name for
+ * the columns in the table.
+ */
+Widgets.Table = function(message, data, columns)
+{
+ Widgets.BaseWidget.call(this, message);
+ this.data = data;
+ this.columns = columns;
+};
+
+Widgets.Table.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
+{
+ /**
+ * Array of objects that holds the data to output in the table.
+ * @type array
+ */
+ data: null,
+
+ /**
+ * Object containing the key value pair of the id and display name for
+ * the columns in the table.
+ * @type object
+ */
+ columns: null,
+
+ render: function() {
+ if (this.element) {
+ return this;
+ }
+
+ let result = this.element = this.document.createElementNS(XHTML_NS, "div");
+ result.className = "consoletable devtools-monospace";
+
+ this.table = new TableWidget(result, {
+ initialColumns: this.columns,
+ uniqueId: "_index",
+ firstColumn: "_index"
+ });
+
+ for (let row of this.data) {
+ this.table.push(row);
+ }
+
+ return this;
+ }
+}); // Widgets.Table.prototype
+
+function gSequenceId()
+{
+ return gSequenceId.n++;
+}
+gSequenceId.n = 0;
+
+exports.ConsoleOutput = ConsoleOutput;
+exports.Messages = Messages;
+exports.Widgets = Widgets;
diff --git a/browser/devtools/webconsole/HUDService.jsm b/browser/devtools/webconsole/hudservice.js
index 3eb54073b..e8ef0fe65 100644
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/hudservice.js
@@ -1,4 +1,4 @@
-/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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
@@ -6,35 +6,20 @@
"use strict";
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
+const {Cc, Ci, Cu} = require("chrome");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
+let Heritage = require("sdk/core/heritage");
-XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
- "resource:///modules/devtools/gDevTools.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "devtools",
- "resource://gre/modules/devtools/Loader.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Services",
- "resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
- "resource://gre/modules/devtools/dbg-server.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
- "resource://gre/modules/devtools/dbg-client.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
- "resource://gre/modules/devtools/WebConsoleUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "promise",
- "resource://gre/modules/commonjs/sdk/core/promise.js", "Promise");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Heritage",
- "resource:///modules/devtools/ViewHelpers.jsm");
+loader.lazyGetter(this, "Telemetry", () => require("devtools/shared/telemetry"));
+loader.lazyGetter(this, "WebConsoleFrame", () => require("devtools/webconsole/webconsole").WebConsoleFrame);
+loader.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
+loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
+loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
+loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
+loader.lazyImporter(this, "DebuggerServer", "resource://gre/modules/devtools/dbg-server.jsm");
+loader.lazyImporter(this, "DebuggerClient", "resource://gre/modules/devtools/dbg-client.jsm");
+loader.lazyGetter(this, "showDoorhanger", () => require("devtools/shared/doorhanger").showDoorhanger);
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
@@ -44,32 +29,38 @@ const BROWSER_CONSOLE_WINDOW_FEATURES = "chrome,titlebar,toolbar,centerscreen,re
// The preference prefix for all of the Browser Console filters.
const BROWSER_CONSOLE_FILTER_PREFS_PREFIX = "devtools.browserconsole.filter.";
-this.EXPORTED_SYMBOLS = ["HUDService"];
+let gHudId = 0;
///////////////////////////////////////////////////////////////////////////
//// The HUD service
function HUD_SERVICE()
{
- this.hudReferences = {};
+ this.consoles = new Map();
+ this.lastFinishedRequest = { callback: null };
}
HUD_SERVICE.prototype =
{
+ _browserConsoleID: null,
+ _browserConsoleDefer: null,
+
/**
- * Keeps a reference for each HeadsUpDisplay that is created
- * @type object
+ * Keeps a reference for each Web Console / Browser Console that is created.
+ * @type Map
*/
- hudReferences: null,
+ consoles: null,
/**
- * getter for UI commands to be used by the frontend
+ * Assign a function to this property to listen for every request that
+ * completes. Used by unit tests. The callback takes one argument: the HTTP
+ * activity object as received from the remote Web Console.
*
- * @returns object
+ * @type object
+ * Includes a property named |callback|. Assign the function to the
+ * |callback| property of this object.
*/
- get consoleUI() {
- return HeadsUpDisplayUICommands;
- },
+ lastFinishedRequest: null,
/**
* Firefox-specific current tab getter
@@ -98,14 +89,14 @@ HUD_SERVICE.prototype =
function HS_openWebConsole(aTarget, aIframeWindow, aChromeWindow)
{
let hud = new WebConsole(aTarget, aIframeWindow, aChromeWindow);
- this.hudReferences[hud.hudId] = hud;
+ this.consoles.set(hud.hudId, hud);
return hud.init();
},
/**
* Open a Browser Console for the given target.
*
- * @see devtools/framework/Target.jsm for details about targets.
+ * @see devtools/framework/target.js for details about targets.
*
* @param object aTarget
* The target that the browser console will connect to.
@@ -120,19 +111,20 @@ HUD_SERVICE.prototype =
function HS_openBrowserConsole(aTarget, aIframeWindow, aChromeWindow)
{
let hud = new BrowserConsole(aTarget, aIframeWindow, aChromeWindow);
- this.hudReferences[hud.hudId] = hud;
+ this._browserConsoleID = hud.hudId;
+ this.consoles.set(hud.hudId, hud);
return hud.init();
},
/**
- * Returns the HeadsUpDisplay object associated to a content window.
+ * Returns the Web Console object associated to a content window.
*
* @param nsIDOMWindow aContentWindow
* @returns object
*/
getHudByWindow: function HS_getHudByWindow(aContentWindow)
{
- for each (let hud in this.hudReferences) {
+ for (let [hudId, hud] of this.consoles) {
let target = hud.target;
if (target && target.tab && target.window === aContentWindow) {
return hud;
@@ -142,37 +134,149 @@ HUD_SERVICE.prototype =
},
/**
- * Returns the hudId that is corresponding to the hud activated for the
- * passed aContentWindow. If there is no matching hudId null is returned.
+ * Returns the console instance for a given id.
*
- * @param nsIDOMWindow aContentWindow
- * @returns string or null
+ * @param string aId
+ * @returns Object
*/
- getHudIdByWindow: function HS_getHudIdByWindow(aContentWindow)
+ getHudReferenceById: function HS_getHudReferenceById(aId)
{
- let hud = this.getHudByWindow(aContentWindow);
- return hud ? hud.hudId : null;
+ return this.consoles.get(aId);
},
/**
- * Returns the hudReference for a given id.
- *
- * @param string aId
- * @returns Object
+ * Find if there is a Web Console open for the current tab and return the
+ * instance.
+ * @return object|null
+ * The WebConsole object or null if the active tab has no open Web
+ * Console.
*/
- getHudReferenceById: function HS_getHudReferenceById(aId)
+ getOpenWebConsole: function HS_getOpenWebConsole()
{
- return aId in this.hudReferences ? this.hudReferences[aId] : null;
+ let tab = this.currentContext().gBrowser.selectedTab;
+ if (!tab || !devtools.TargetFactory.isKnownTab(tab)) {
+ return null;
+ }
+ let target = devtools.TargetFactory.forTab(tab);
+ let toolbox = gDevTools.getToolbox(target);
+ let panel = toolbox ? toolbox.getPanel("webconsole") : null;
+ return panel ? panel.hud : null;
},
/**
- * Assign a function to this property to listen for every request that
- * completes. Used by unit tests. The callback takes one argument: the HTTP
- * activity object as received from the remote Web Console.
+ * Toggle the Browser Console.
+ */
+ toggleBrowserConsole: function HS_toggleBrowserConsole()
+ {
+ if (this._browserConsoleID) {
+ let hud = this.getHudReferenceById(this._browserConsoleID);
+ return hud.destroy();
+ }
+
+ if (this._browserConsoleDefer) {
+ return this._browserConsoleDefer.promise;
+ }
+
+ this._browserConsoleDefer = promise.defer();
+
+ function connect()
+ {
+ let deferred = promise.defer();
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ let client = new DebuggerClient(DebuggerServer.connectPipe());
+ client.connect(() =>
+ client.listTabs((aResponse) => {
+ // Add Global Process debugging...
+ let globals = Cu.cloneInto(aResponse, {});
+ delete globals.tabs;
+ delete globals.selected;
+ // ...only if there are appropriate actors (a 'from' property will
+ // always be there).
+ if (Object.keys(globals).length > 1) {
+ deferred.resolve({ form: globals, client: client, chrome: true });
+ } else {
+ deferred.reject("Global console not found!");
+ }
+ }));
+
+ return deferred.promise;
+ }
+
+ let target;
+ function getTarget(aConnection)
+ {
+ let options = {
+ form: aConnection.form,
+ client: aConnection.client,
+ chrome: true,
+ };
+
+ return devtools.TargetFactory.forRemoteTab(options);
+ }
+
+ function openWindow(aTarget)
+ {
+ target = aTarget;
+
+ let deferred = promise.defer();
+
+ let win = Services.ww.openWindow(null, devtools.Tools.webConsole.url, "_blank",
+ BROWSER_CONSOLE_WINDOW_FEATURES, null);
+ win.addEventListener("DOMContentLoaded", function onLoad() {
+ win.removeEventListener("DOMContentLoaded", onLoad);
+
+ // Set the correct Browser Console title.
+ let root = win.document.documentElement;
+ root.setAttribute("title", root.getAttribute("browserConsoleTitle"));
+
+ deferred.resolve(win);
+ });
+
+ return deferred.promise;
+ }
+
+ connect().then(getTarget).then(openWindow).then((aWindow) => {
+ this.openBrowserConsole(target, aWindow, aWindow)
+ .then((aBrowserConsole) => {
+ this._browserConsoleDefer.resolve(aBrowserConsole);
+ this._browserConsoleDefer = null;
+ })
+ }, console.error);
+
+ return this._browserConsoleDefer.promise;
+ },
+
+ /**
+ * Opens or focuses the Browser Console.
+ */
+ openBrowserConsoleOrFocus: function HS_openBrowserConsoleOrFocus()
+ {
+ let hud = this.getBrowserConsole();
+ if (hud) {
+ hud.iframeWindow.focus();
+ return promise.resolve(hud);
+ }
+ else {
+ return this.toggleBrowserConsole();
+ }
+ },
+
+ /**
+ * Get the Browser Console instance, if open.
*
- * @type function
+ * @return object|null
+ * A BrowserConsole instance or null if the Browser Console is not
+ * open.
*/
- lastFinishedRequestCallback: null,
+ getBrowserConsole: function HS_getBrowserConsole()
+ {
+ return this.getHudReferenceById(this._browserConsoleID);
+ },
};
@@ -197,7 +301,7 @@ function WebConsole(aTarget, aIframeWindow, aChromeWindow)
{
this.iframeWindow = aIframeWindow;
this.chromeWindow = aChromeWindow;
- this.hudId = "hud_" + Date.now();
+ this.hudId = "hud_" + ++gHudId;
this.target = aTarget;
this.browserWindow = this.chromeWindow.top;
@@ -207,7 +311,7 @@ function WebConsole(aTarget, aIframeWindow, aChromeWindow)
this.browserWindow = HUDService.currentContext();
}
- this.ui = new this.iframeWindow.WebConsoleFrame(this);
+ this.ui = new WebConsoleFrame(this);
}
WebConsole.prototype = {
@@ -221,12 +325,29 @@ WebConsole.prototype = {
_destroyer: null,
/**
- * Getter for HUDService.lastFinishedRequestCallback.
+ * Getter for a function to to listen for every request that completes. Used
+ * by unit tests. The callback takes one argument: the HTTP activity object as
+ * received from the remote Web Console.
*
- * @see HUDService.lastFinishedRequestCallback
* @type function
*/
- get lastFinishedRequestCallback() HUDService.lastFinishedRequestCallback,
+ get lastFinishedRequestCallback() HUDService.lastFinishedRequest.callback,
+
+ /**
+ * Getter for the window that can provide various utilities that the web
+ * console makes use of, like opening links, managing popups, etc. In
+ * most cases, this will be |this.browserWindow|, but in some uses (such as
+ * the Browser Toolbox), there is no browser window, so an alternative window
+ * hosts the utilities there.
+ * @type nsIDOMWindow
+ */
+ get chromeUtilsWindow()
+ {
+ if (this.browserWindow) {
+ return this.browserWindow;
+ }
+ return this.chromeWindow.top;
+ },
/**
* Getter for the xul:popupset that holds any popups we open.
@@ -234,7 +355,7 @@ WebConsole.prototype = {
*/
get mainPopupSet()
{
- return this.browserWindow.document.getElementById("mainPopupSet");
+ return this.chromeUtilsWindow.document.getElementById("mainPopupSet");
},
/**
@@ -246,7 +367,10 @@ WebConsole.prototype = {
return this.ui ? this.ui.outputNode : null;
},
- get gViewSourceUtils() this.browserWindow.gViewSourceUtils,
+ get gViewSourceUtils()
+ {
+ return this.chromeUtilsWindow.gViewSourceUtils;
+ },
/**
* Initialize the Web Console instance.
@@ -309,7 +433,7 @@ WebConsole.prototype = {
*/
openLink: function WC_openLink(aLink)
{
- this.browserWindow.openUILinkIn(aLink, "tab");
+ this.chromeUtilsWindow.openUILinkIn(aLink, "tab");
},
/**
@@ -370,55 +494,74 @@ WebConsole.prototype = {
viewSourceInDebugger:
function WC_viewSourceInDebugger(aSourceURL, aSourceLine)
{
- let self = this;
- let panelWin = null;
- let debuggerWasOpen = true;
let toolbox = gDevTools.getToolbox(this.target);
if (!toolbox) {
- self.viewSource(aSourceURL, aSourceLine);
+ this.viewSource(aSourceURL, aSourceLine);
return;
}
- if (!toolbox.getPanel("jsdebugger")) {
- debuggerWasOpen = false;
- let toolboxWin = toolbox.doc.defaultView;
- toolboxWin.addEventListener("Debugger:AfterSourcesAdded",
- function afterSourcesAdded() {
- toolboxWin.removeEventListener("Debugger:AfterSourcesAdded",
- afterSourcesAdded);
- loadScript();
- });
+ let showSource = ({ DebuggerView }) => {
+ let item = DebuggerView.Sources.getItemForAttachment(
+ a => a.source.url === aSourceURL
+ );
+ if (item) {
+ DebuggerView.setEditorLocation(item.attachment.source.actor, aSourceLine,
+ { noDebug: true }).then(() => {
+ this.ui.emit("source-in-debugger-opened");
+ });
+ return;
+ }
+ toolbox.selectTool("webconsole")
+ .then(() => this.viewSource(aSourceURL, aSourceLine));
}
- toolbox.selectTool("jsdebugger").then(function onDebuggerOpen(dbg) {
- panelWin = dbg.panelWin;
- if (debuggerWasOpen) {
- loadScript();
+ // If the Debugger was already open, switch to it and try to show the
+ // source immediately. Otherwise, initialize it and wait for the sources
+ // to be added first.
+ let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger");
+ toolbox.selectTool("jsdebugger").then(({ panelWin: dbg }) => {
+ if (debuggerAlreadyOpen) {
+ showSource(dbg);
+ } else {
+ dbg.once(dbg.EVENTS.SOURCES_ADDED, () => showSource(dbg));
}
});
+ },
- function loadScript() {
- let debuggerView = panelWin.DebuggerView;
- if (!debuggerView.Sources.containsValue(aSourceURL)) {
- toolbox.selectTool("webconsole");
- self.viewSource(aSourceURL, aSourceLine);
- return;
- }
- if (debuggerWasOpen && debuggerView.Sources.selectedValue == aSourceURL) {
- debuggerView.editor.setCaretPosition(aSourceLine - 1);
+
+ /**
+ * Tries to open a JavaScript file related to the web page for the web console
+ * instance in the corresponding Scratchpad.
+ *
+ * @param string aSourceURL
+ * The URL of the file which corresponds to a Scratchpad id.
+ */
+ viewSourceInScratchpad: function WC_viewSourceInScratchpad(aSourceURL)
+ {
+ // Check for matching top level Scratchpad window.
+ let wins = Services.wm.getEnumerator("devtools:scratchpad");
+
+ while (wins.hasMoreElements()) {
+ let win = wins.getNext();
+
+ if (!win.closed && win.Scratchpad.uniqueName === aSourceURL) {
+ win.focus();
return;
}
-
- panelWin.addEventListener("Debugger:SourceShown", onSource, false);
- debuggerView.Sources.preferredSource = aSourceURL;
}
- function onSource(aEvent) {
- if (aEvent.detail.url != aSourceURL) {
- return;
+ // Check for matching Scratchpad toolbox tab.
+ for (let [, toolbox] of gDevTools) {
+ let scratchpadPanel = toolbox.getPanel("scratchpad");
+ if (scratchpadPanel) {
+ let { scratchpad } = scratchpadPanel;
+ if (scratchpad.uniqueName === aSourceURL) {
+ toolbox.selectTool("scratchpad");
+ toolbox.raise();
+ scratchpad.editor.focus();
+ return;
+ }
}
- panelWin.removeEventListener("Debugger:SourceShown", onSource, false);
- panelWin.DebuggerView.editor.setCaretPosition(aSourceLine - 1);
}
},
@@ -445,18 +588,42 @@ WebConsole.prototype = {
if (!panel) {
return null;
}
- let framesController = panel.panelWin.gStackFrames;
+ let framesController = panel.panelWin.DebuggerController.StackFrames;
let thread = framesController.activeThread;
if (thread && thread.paused) {
return {
frames: thread.cachedFrames,
- selected: framesController.currentFrame,
+ selected: framesController.currentFrameDepth,
};
}
return null;
},
/**
+ * Retrieves the current selection from the Inspector, if such a selection
+ * exists. This is used to pass the ID of the selected actor to the Web
+ * Console server for the $0 helper.
+ *
+ * @return object|null
+ * A Selection referring to the currently selected node in the
+ * Inspector.
+ * If the inspector was never opened, or no node was ever selected,
+ * then |null| is returned.
+ */
+ getInspectorSelection: function WC_getInspectorSelection()
+ {
+ let toolbox = gDevTools.getToolbox(this.target);
+ if (!toolbox) {
+ return null;
+ }
+ let panel = toolbox.getPanel("inspector");
+ if (!panel || !panel.selection) {
+ return null;
+ }
+ return panel.selection;
+ },
+
+ /**
* Destroy the object. Call this method to avoid memory leaks when the Web
* Console is closed.
*
@@ -469,7 +636,7 @@ WebConsole.prototype = {
return this._destroyer.promise;
}
- delete HUDService.hudReferences[this.hudId];
+ HUDService.consoles.delete(this.hudId);
this._destroyer = promise.defer();
@@ -525,6 +692,7 @@ WebConsole.prototype = {
function BrowserConsole()
{
WebConsole.apply(this, arguments);
+ this._telemetry = new Telemetry();
}
BrowserConsole.prototype = Heritage.extend(WebConsole.prototype,
@@ -555,6 +723,7 @@ BrowserConsole.prototype = Heritage.extend(WebConsole.prototype,
// instance.
let onClose = () => {
window.removeEventListener("unload", onClose);
+ window.removeEventListener("focus", onFocus);
this.destroy();
};
window.addEventListener("unload", onClose);
@@ -562,6 +731,14 @@ BrowserConsole.prototype = Heritage.extend(WebConsole.prototype,
// Make sure Ctrl-W closes the Browser Console window.
window.document.getElementById("cmd_close").removeAttribute("disabled");
+ this._telemetry.toolOpened("browserconsole");
+
+ // Create an onFocus handler just to display the dev edition promo.
+ // This is to prevent race conditions in some environments.
+ // Hook to display promotional Developer Edition doorhanger. Only displayed once.
+ let onFocus = () => showDoorhanger({ window, type: "deveditionpromo" });
+ window.addEventListener("focus", onFocus);
+
this._bc_init = this.$init();
return this._bc_init;
},
@@ -572,7 +749,7 @@ BrowserConsole.prototype = Heritage.extend(WebConsole.prototype,
* Destroy the object.
*
* @return object
- * A Promise object that is resolved once the Browser Console is closed.
+ * A promise object that is resolved once the Browser Console is closed.
*/
destroy: function BC_destroy()
{
@@ -580,12 +757,14 @@ BrowserConsole.prototype = Heritage.extend(WebConsole.prototype,
return this._bc_destroyer.promise;
}
+ this._telemetry.toolClosed("browserconsole");
+
this._bc_destroyer = promise.defer();
let chromeWindow = this.chromeWindow;
this.$destroy().then(() =>
this.target.client.close(() => {
- HeadsUpDisplayUICommands._browserConsoleID = null;
+ HUDService._browserConsoleID = null;
chromeWindow.close();
this._bc_destroyer.resolve(null);
}));
@@ -594,144 +773,17 @@ BrowserConsole.prototype = Heritage.extend(WebConsole.prototype,
},
});
-
-//////////////////////////////////////////////////////////////////////////
-// HeadsUpDisplayUICommands
-//////////////////////////////////////////////////////////////////////////
-
-var HeadsUpDisplayUICommands = {
- _browserConsoleID: null,
- _browserConsoleDefer: null,
-
- /**
- * Toggle the Web Console for the current tab.
- *
- * @return object
- * A promise for either the opening of the toolbox that holds the Web
- * Console, or a promise for the closing of the toolbox.
- */
- toggleHUD: function UIC_toggleHUD()
- {
- let window = HUDService.currentContext();
- let target = devtools.TargetFactory.forTab(window.gBrowser.selectedTab);
- let toolbox = gDevTools.getToolbox(target);
-
- return toolbox && toolbox.currentToolId == "webconsole" ?
- toolbox.destroy() :
- gDevTools.showToolbox(target, "webconsole");
- },
-
- /**
- * Find if there is a Web Console open for the current tab and return the
- * instance.
- * @return object|null
- * The WebConsole object or null if the active tab has no open Web
- * Console.
- */
- getOpenHUD: function UIC_getOpenHUD()
- {
- let tab = HUDService.currentContext().gBrowser.selectedTab;
- if (!tab || !devtools.TargetFactory.isKnownTab(tab)) {
- return null;
- }
- let target = devtools.TargetFactory.forTab(tab);
- let toolbox = gDevTools.getToolbox(target);
- let panel = toolbox ? toolbox.getPanel("webconsole") : null;
- return panel ? panel.hud : null;
- },
-
- /**
- * Toggle the Browser Console.
- */
- toggleBrowserConsole: function UIC_toggleBrowserConsole()
- {
- if (this._browserConsoleID) {
- let hud = HUDService.getHudReferenceById(this._browserConsoleID);
- return hud.destroy();
- }
-
- if (this._browserConsoleDefer) {
- return this._browserConsoleDefer.promise;
- }
-
- this._browserConsoleDefer = promise.defer();
-
- function connect()
- {
- let deferred = promise.defer();
-
- if (!DebuggerServer.initialized) {
- DebuggerServer.init();
- DebuggerServer.addBrowserActors();
- }
-
- let client = new DebuggerClient(DebuggerServer.connectPipe());
- client.connect(() =>
- client.listTabs((aResponse) => {
- // Add Global Process debugging...
- let globals = JSON.parse(JSON.stringify(aResponse));
- delete globals.tabs;
- delete globals.selected;
- // ...only if there are appropriate actors (a 'from' property will
- // always be there).
- if (Object.keys(globals).length > 1) {
- deferred.resolve({ form: globals, client: client, chrome: true });
- } else {
- deferred.reject("Global console not found!");
- }
- }));
-
- return deferred.promise;
- }
-
- let target;
- function getTarget(aConnection)
- {
- let options = {
- form: aConnection.form,
- client: aConnection.client,
- chrome: true,
- };
-
- return devtools.TargetFactory.forRemoteTab(options);
- }
-
- function openWindow(aTarget)
- {
- target = aTarget;
-
- let deferred = promise.defer();
-
- let win = Services.ww.openWindow(null, devtools.Tools.webConsole.url, "_blank",
- BROWSER_CONSOLE_WINDOW_FEATURES, null);
- win.addEventListener("DOMContentLoaded", function onLoad() {
- win.removeEventListener("DOMContentLoaded", onLoad);
-
- // Set the correct Browser Console title.
- let root = win.document.documentElement;
- root.setAttribute("title", root.getAttribute("browserConsoleTitle"));
-
- deferred.resolve(win);
- });
-
- return deferred.promise;
- }
-
- connect().then(getTarget).then(openWindow).then((aWindow) =>
- HUDService.openBrowserConsole(target, aWindow, aWindow)
- .then((aBrowserConsole) => {
- this._browserConsoleID = aBrowserConsole.hudId;
- this._browserConsoleDefer.resolve(aBrowserConsole);
- this._browserConsoleDefer = null;
- }));
-
- return this._browserConsoleDefer.promise;
- },
-
- get browserConsole() {
- return HUDService.getHudReferenceById(this._browserConsoleID);
- },
-};
-
const HUDService = new HUD_SERVICE();
+(() => {
+ let methods = ["openWebConsole", "openBrowserConsole",
+ "toggleBrowserConsole", "getOpenWebConsole",
+ "getBrowserConsole", "getHudByWindow",
+ "openBrowserConsoleOrFocus", "getHudReferenceById"];
+ for (let method of methods) {
+ exports[method] = HUDService[method].bind(HUDService);
+ }
+
+ exports.consoles = HUDService.consoles;
+ exports.lastFinishedRequest = HUDService.lastFinishedRequest;
+})();
diff --git a/browser/devtools/webconsole/moz.build b/browser/devtools/webconsole/moz.build
index 6ecebe123..6d42322e3 100644
--- a/browser/devtools/webconsole/moz.build
+++ b/browser/devtools/webconsole/moz.build
@@ -4,5 +4,13 @@
# 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/.
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
-
+EXTRA_JS_MODULES.devtools.webconsole += [
+ 'console-commands.js',
+ 'console-output.js',
+ 'hudservice.js',
+ 'network-panel.js',
+ 'panel.js',
+ 'webconsole.js',
+]
diff --git a/browser/devtools/webconsole/NetworkPanel.jsm b/browser/devtools/webconsole/network-panel.js
index e6d634f7b..626241b1d 100644
--- a/browser/devtools/webconsole/NetworkPanel.jsm
+++ b/browser/devtools/webconsole/network-panel.js
@@ -1,4 +1,4 @@
-/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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,
@@ -6,28 +6,17 @@
"use strict";
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
+const {Cc, Ci, Cu} = require("chrome");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+loader.lazyGetter(this, "NetworkHelper", () => require("devtools/toolkit/webconsole/network-helper"));
+loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
+loader.lazyServiceGetter(this, "mimeService", "@mozilla.org/mime;1", "nsIMIMEService");
-XPCOMUtils.defineLazyServiceGetter(this, "mimeService", "@mozilla.org/mime;1",
- "nsIMIMEService");
-
-XPCOMUtils.defineLazyModuleGetter(this, "NetworkHelper",
- "resource://gre/modules/devtools/NetworkHelper.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
- "resource://gre/modules/NetUtil.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
- "resource://gre/modules/devtools/WebConsoleUtils.jsm");
+let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
-this.EXPORTED_SYMBOLS = ["NetworkPanel"];
/**
* Creates a new NetworkPanel.
@@ -41,7 +30,6 @@ this.EXPORTED_SYMBOLS = ["NetworkPanel"];
* The parent WebConsoleFrame object that owns this network panel
* instance.
*/
-this.NetworkPanel =
function NetworkPanel(aParent, aHttpActivity, aWebConsoleFrame)
{
let doc = aParent.ownerDocument;
@@ -108,6 +96,7 @@ function NetworkPanel(aParent, aHttpActivity, aWebConsoleFrame)
aParent.appendChild(this.panel);
}
+exports.NetworkPanel = NetworkPanel;
NetworkPanel.prototype =
{
@@ -297,7 +286,7 @@ NetworkPanel.prototype =
return a.name.toLowerCase() < b.name.toLowerCase();
});
- aList.forEach(function(aItem) {
+ aList.forEach((aItem) => {
let name = aItem.name;
if (aIgnoreCookie && (name == "Cookie" || name == "Set-Cookie")) {
return;
@@ -343,7 +332,7 @@ NetworkPanel.prototype =
row.appendChild(td);
parent.appendChild(row);
- }.bind(this));
+ });
},
/**
@@ -623,7 +612,7 @@ NetworkPanel.prototype =
let content = this.httpActivity.response.content;
let longString = this.webconsole.webConsoleClient.longString(content.text);
longString.substring(longString.initial.length, longString.length,
- function NP__onLongStringSubstring(aResponse)
+ (aResponse) =>
{
if (aResponse.error) {
Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
@@ -643,7 +632,7 @@ NetworkPanel.prototype =
this._appendTextNode("responseBody" + cached + "Content",
aResponse.substring);
}
- }.bind(this));
+ });
},
/**
@@ -790,7 +779,7 @@ NetworkPanel.prototype =
let postData = this.httpActivity.request.postData;
let longString = this.webconsole.webConsoleClient.longString(postData.text);
longString.substring(longString.initial.length, longString.length,
- function NP__onLongStringSubstring(aResponse)
+ (aResponse) =>
{
if (aResponse.error) {
Cu.reportError("NP__onLongStringSubstring error: " + aResponse.error);
@@ -799,7 +788,7 @@ NetworkPanel.prototype =
postData.text = postData.text.initial + aResponse.substring;
this._updateRequestBody();
- }.bind(this));
+ });
},
};
diff --git a/browser/devtools/webconsole/WebConsolePanel.jsm b/browser/devtools/webconsole/panel.js
index 03f421b42..f5c0e0cfd 100644
--- a/browser/devtools/webconsole/WebConsolePanel.jsm
+++ b/browser/devtools/webconsole/panel.js
@@ -4,34 +4,45 @@
"use strict";
-this.EXPORTED_SYMBOLS = [ "WebConsolePanel" ];
+const {Cc, Ci, Cu} = require("chrome");
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "promise",
- "resource://gre/modules/commonjs/sdk/core/promise.js", "Promise");
-
-XPCOMUtils.defineLazyModuleGetter(this, "HUDService",
- "resource:///modules/HUDService.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
- "resource:///modules/devtools/shared/event-emitter.js");
+loader.lazyImporter(this, "devtools", "resource://gre/modules/devtools/Loader.jsm");
+loader.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
+loader.lazyGetter(this, "HUDService", () => require("devtools/webconsole/hudservice"));
+loader.lazyGetter(this, "EventEmitter", () => require("devtools/toolkit/event-emitter"));
+loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
/**
* A DevToolPanel that controls the Web Console.
*/
-function WebConsolePanel(iframeWindow, toolbox) {
+function WebConsolePanel(iframeWindow, toolbox)
+{
this._frameWindow = iframeWindow;
this._toolbox = toolbox;
EventEmitter.decorate(this);
}
+exports.WebConsolePanel = WebConsolePanel;
+
WebConsolePanel.prototype = {
hud: null,
/**
+ * Called by the WebConsole's onkey command handler.
+ * If the WebConsole is opened, check if the JSTerm's input line has focus.
+ * If not, focus it.
+ */
+ focusInput: function WCP_focusInput()
+ {
+ let inputNode = this.hud.jsterm.inputNode;
+
+ if (!inputNode.getAttribute("focused"))
+ {
+ inputNode.focus();
+ }
+ },
+
+ /**
* Open is effectively an asynchronous constructor.
*
* @return object
@@ -41,7 +52,6 @@ WebConsolePanel.prototype = {
{
let parentDoc = this._toolbox.doc;
let iframe = parentDoc.getElementById("toolbox-panel-iframe-webconsole");
- iframe.className = "web-console-frame";
// Make sure the iframe content window is ready.
let deferredIframe = promise.defer();
diff --git a/browser/devtools/webconsole/test/browser.ini b/browser/devtools/webconsole/test/browser.ini
new file mode 100644
index 000000000..4dca6e15e
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -0,0 +1,380 @@
+[DEFAULT]
+subsuite = devtools
+support-files =
+ head.js
+ test-bug-585956-console-trace.html
+ test-bug-593003-iframe-wrong-hud-iframe.html
+ test-bug-593003-iframe-wrong-hud.html
+ test-bug-595934-canvas-css.html
+ test-bug-595934-canvas-css.js
+ test-bug-595934-css-loader.css
+ test-bug-595934-css-loader.css^headers^
+ test-bug-595934-css-loader.html
+ test-bug-595934-css-parser.css
+ test-bug-595934-css-parser.html
+ test-bug-595934-empty-getelementbyid.html
+ test-bug-595934-empty-getelementbyid.js
+ test-bug-595934-html.html
+ test-bug-595934-image.html
+ test-bug-595934-image.jpg
+ test-bug-595934-imagemap.html
+ test-bug-595934-malformedxml-external.html
+ test-bug-595934-malformedxml-external.xml
+ test-bug-595934-malformedxml.xhtml
+ test-bug-595934-svg.xhtml
+ test-bug-595934-workers.html
+ test-bug-595934-workers.js
+ test-bug-597136-external-script-errors.html
+ test-bug-597136-external-script-errors.js
+ test-bug-597756-reopen-closed-tab.html
+ test-bug-599725-response-headers.sjs
+ test-bug-600183-charset.html
+ test-bug-600183-charset.html^headers^
+ test-bug-601177-log-levels.html
+ test-bug-601177-log-levels.js
+ test-bug-603750-websocket.html
+ test-bug-603750-websocket.js
+ test-bug-613013-console-api-iframe.html
+ test-bug-618078-network-exceptions.html
+ test-bug-621644-jsterm-dollar.html
+ test-bug-630733-response-redirect-headers.sjs
+ test-bug-632275-getters.html
+ test-bug-632347-iterators-generators.html
+ test-bug-644419-log-limits.html
+ test-bug-646025-console-file-location.html
+ test-bug-658368-time-methods.html
+ test-bug-737873-mixedcontent.html
+ test-bug-752559-ineffective-iframe-sandbox-warning0.html
+ test-bug-752559-ineffective-iframe-sandbox-warning1.html
+ test-bug-752559-ineffective-iframe-sandbox-warning2.html
+ test-bug-752559-ineffective-iframe-sandbox-warning3.html
+ test-bug-752559-ineffective-iframe-sandbox-warning4.html
+ test-bug-752559-ineffective-iframe-sandbox-warning5.html
+ test-bug-752559-ineffective-iframe-sandbox-warning-inner.html
+ test-bug-752559-ineffective-iframe-sandbox-warning-nested1.html
+ test-bug-752559-ineffective-iframe-sandbox-warning-nested2.html
+ test-bug-762593-insecure-passwords-about-blank-web-console-warning.html
+ test-bug-762593-insecure-passwords-web-console-warning.html
+ test-bug-766001-console-log.js
+ test-bug-766001-js-console-links.html
+ test-bug-766001-js-errors.js
+ test-bug-782653-css-errors-1.css
+ test-bug-782653-css-errors-2.css
+ test-bug-782653-css-errors.html
+ test-bug-837351-security-errors.html
+ test-bug-846918-hsts-invalid-headers.html
+ test-bug-846918-hsts-invalid-headers.html^headers^
+ test-bug-859170-longstring-hang.html
+ test-bug-869003-iframe.html
+ test-bug-869003-top-window.html
+ test-closure-optimized-out.html
+ test-closures.html
+ test-console-assert.html
+ test-console-count.html
+ test-console-count-external-file.js
+ test-console-extras.html
+ test-console-replaced-api.html
+ test-console.html
+ test-console-table.html
+ test-console-output-02.html
+ test-console-output-03.html
+ test-console-output-04.html
+ test-console-output-dom-elements.html
+ test-console-output-events.html
+ test-console-column.html
+ test-consoleiframes.html
+ test-certificate-messages.html
+ test-data.json
+ test-data.json^headers^
+ test-duplicate-error.html
+ test-encoding-ISO-8859-1.html
+ test-error.html
+ test-eval-in-stackframe.html
+ test-file-location.js
+ test-filter.html
+ test-for-of.html
+ test-iframe-762593-insecure-form-action.html
+ test-iframe-762593-insecure-frame.html
+ test-iframe1.html
+ test-iframe2.html
+ test-iframe3.html
+ test-image.png
+ test-mixedcontent-securityerrors.html
+ test-mutation.html
+ test-network-request.html
+ test-network.html
+ test-observe-http-ajax.html
+ test-own-console.html
+ test-property-provider.html
+ test-repeated-messages.html
+ test-result-format-as-string.html
+ test-webconsole-error-observer.html
+ test_bug_770099_violation.html
+ test_bug_770099_violation.html^headers^
+ test-autocomplete-in-stackframe.html
+ testscript.js
+ test-bug_923281_console_log_filter.html
+ test-bug_923281_test1.js
+ test-bug_923281_test2.js
+ test-bug_939783_console_trace_duplicates.html
+ test-bug-952277-highlight-nodes-in-vview.html
+ test-bug-609872-cd-iframe-parent.html
+ test-bug-609872-cd-iframe-child.html
+ test-bug-989025-iframe-parent.html
+ test-console-api-stackframe.html
+ test_bug_1010953_cspro.html^headers^
+ test_bug_1010953_cspro.html
+ test_bug1045902_console_csp_ignore_reflected_xss_message.html^headers^
+ test_bug1045902_console_csp_ignore_reflected_xss_message.html
+ test_bug1092055_shouldwarn.js^headers^
+ test_bug1092055_shouldwarn.js
+ test_bug1092055_shouldwarn.html
+
+[browser_bug1045902_console_csp_ignore_reflected_xss_message.js]
+[browser_bug664688_sandbox_update_after_navigation.js]
+skip-if = e10s # Bug 1042253 - webconsole e10s tests (intermittent Linux debug)
+[browser_bug_638949_copy_link_location.js]
+[browser_bug_862916_console_dir_and_filter_off.js]
+[browser_bug_865288_repeat_different_objects.js]
+[browser_bug_865871_variables_view_close_on_esc_key.js]
+[browser_bug_869003_inspect_cross_domain_object.js]
+[browser_bug_871156_ctrlw_close_tab.js]
+skip-if = e10s # Bug 1042253 - webconsole e10s tests (intermittent Linux debug)
+[browser_cached_messages.js]
+skip-if = buildapp == 'mulet' || e10s # Bug 1042253 - webconsole e10s tests (expectUncaughtException)
+[browser_console.js]
+[browser_console_addonsdk_loader_exception.js]
+[browser_console_clear_on_reload.js]
+[browser_console_click_focus.js]
+[browser_console_consolejsm_output.js]
+[browser_console_copy_command.js]
+[browser_console_dead_objects.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_console_copy_entire_message_context_menu.js]
+[browser_console_error_source_click.js]
+skip-if = buildapp == 'mulet' || e10s # Bug 1042253 - webconsole e10s tests
+[browser_console_filters.js]
+[browser_console_iframe_messages.js]
+skip-if = buildapp == 'mulet' || e10s # Bug 1042253 - webconsole e10s tests
+[browser_console_keyboard_accessibility.js]
+[browser_console_log_inspectable_object.js]
+[browser_console_native_getters.js]
+[browser_console_navigation_marker.js]
+[browser_console_nsiconsolemessage.js]
+skip-if = buildapp == 'mulet'
+[browser_console_optimized_out_vars.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_console_private_browsing.js]
+skip-if = buildapp == 'mulet' || e10s # Bug 1042253 - webconsole e10s tests
+[browser_console_variables_view.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_console_variables_view_dom_nodes.js]
+[browser_console_variables_view_dont_sort_non_sortable_classes_properties.js]
+skip-if = buildapp == 'mulet'
+[browser_console_variables_view_while_debugging.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_console_variables_view_while_debugging_and_inspecting.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_eval_in_debugger_stackframe.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_eval_in_debugger_stackframe2.js]
+[browser_jsterm_inspect.js]
+[browser_longstring_hang.js]
+[browser_netpanel_longstring_expand.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_output_breaks_after_console_dir_uninspectable.js]
+[browser_output_longstring_expand.js]
+[browser_repeated_messages_accuracy.js]
+skip-if = buildapp == 'mulet'
+[browser_result_format_as_string.js]
+[browser_warn_user_about_replaced_api.js]
+[browser_webconsole_abbreviate_source_url.js]
+[browser_webconsole_allow_mixedcontent_securityerrors.js]
+skip-if = buildapp == 'mulet'
+[browser_webconsole_assert.js]
+[browser_webconsole_basic_net_logging.js]
+[browser_webconsole_block_mixedcontent_securityerrors.js]
+skip-if = buildapp == 'mulet'
+[browser_webconsole_bug_579412_input_focus.js]
+[browser_webconsole_bug_580001_closing_after_completion.js]
+[browser_webconsole_bug_580030_errors_after_page_reload.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s (expectUncaughtException)
+[browser_webconsole_bug_580454_timestamp_l10n.js]
+[browser_webconsole_bug_582201_duplicate_errors.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s (expectUncaughtException)
+[browser_webconsole_bug_583816_No_input_and_Tab_key_pressed.js]
+[browser_webconsole_bug_585237_line_limit.js]
+[browser_webconsole_bug_585956_console_trace.js]
+[browser_webconsole_bug_585991_autocomplete_keys.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_bug_585991_autocomplete_popup.js]
+[browser_webconsole_bug_586388_select_all.js]
+[browser_webconsole_bug_587617_output_copy.js]
+[browser_webconsole_bug_588342_document_focus.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_bug_588730_text_node_insertion.js]
+[browser_webconsole_bug_588967_input_expansion.js]
+[browser_webconsole_bug_589162_css_filter.js]
+[browser_webconsole_bug_592442_closing_brackets.js]
+[browser_webconsole_bug_593003_iframe_wrong_hud.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_bug_594477_clickable_output.js]
+[browser_webconsole_bug_594497_history_arrow_keys.js]
+[browser_webconsole_bug_595223_file_uri.js]
+[browser_webconsole_bug_595350_multiple_windows_and_tabs.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_bug_595934_message_categories.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s (expectUncaughtException)
+[browser_webconsole_bug_597103_deactivateHUDForContext_unfocused_window.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_bug_597136_external_script_errors.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s (expectUncaughtException)
+[browser_webconsole_bug_597136_network_requests_from_chrome.js]
+[browser_webconsole_bug_597460_filter_scroll.js]
+[browser_webconsole_bug_597756_reopen_closed_tab.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s (expectUncaughtException)
+[browser_webconsole_bug_599725_response_headers.js]
+[browser_webconsole_bug_600183_charset.js]
+[browser_webconsole_bug_601177_log_levels.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s (expectUncaughtException)
+[browser_webconsole_bug_601352_scroll.js]
+[browser_webconsole_bug_601667_filter_buttons.js]
+[browser_webconsole_bug_602572_log_bodies_checkbox.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_bug_603750_websocket.js]
+[browser_webconsole_bug_611795.js]
+[browser_webconsole_bug_613013_console_api_iframe.js]
+[browser_webconsole_bug_613280_jsterm_copy.js]
+[browser_webconsole_bug_613642_maintain_scroll.js]
+[browser_webconsole_bug_613642_prune_scroll.js]
+[browser_webconsole_bug_614793_jsterm_scroll.js]
+[browser_webconsole_bug_618078_network_exceptions.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s (expectUncaughtException)
+[browser_webconsole_bug_618311_close_panels.js]
+[browser_webconsole_bug_621644_jsterm_dollar.js]
+[browser_webconsole_bug_622303_persistent_filters.js]
+[browser_webconsole_bug_623749_ctrl_a_select_all_winnt.js]
+skip-if = os != "win"
+[browser_webconsole_bug_630733_response_redirect_headers.js]
+[browser_webconsole_bug_632275_getters_document_width.js]
+[browser_webconsole_bug_632347_iterators_generators.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_bug_632817.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_bug_642108_pruneTest.js]
+[browser_webconsole_autocomplete_and_selfxss.js]
+[browser_webconsole_bug_644419_log_limits.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s (expectUncaughtException)
+[browser_webconsole_bug_646025_console_file_location.js]
+[browser_webconsole_bug_651501_document_body_autocomplete.js]
+[browser_webconsole_bug_653531_highlighter_console_helper.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_bug_658368_time_methods.js]
+[browser_webconsole_bug_659907_console_dir.js]
+[browser_webconsole_bug_660806_history_nav.js]
+[browser_webconsole_bug_664131_console_group.js]
+[browser_webconsole_bug_686937_autocomplete_JSTerm_helpers.js]
+[browser_webconsole_bug_704295.js]
+[browser_webconsole_bug_734061_No_input_change_and_Tab_key_pressed.js]
+[browser_webconsole_bug_737873_mixedcontent.js]
+[browser_webconsole_bug_752559_ineffective_iframe_sandbox_warning.js]
+skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
+[browser_webconsole_bug_762593_insecure_passwords_about_blank_web_console_warning.js]
+skip-if = buildapp == 'mulet'
+[browser_webconsole_bug_762593_insecure_passwords_web_console_warning.js]
+skip-if = true # Bug 1110500 - mouse event failure in test
+[browser_webconsole_bug_764572_output_open_url.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_bug_766001_JS_Console_in_Debugger.js]
+skip-if = buildapp == 'mulet' || e10s # Bug 1042253 - webconsole e10s tests (expectUncaughtException)
+[browser_webconsole_bug_770099_violation.js]
+[browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js]
+skip-if = buildapp == 'mulet'
+[browser_webconsole_bug_804845_ctrl_key_nav.js]
+skip-if = os != "mac"
+[browser_webconsole_bug_817834_add_edited_input_to_history.js]
+[browser_webconsole_bug_837351_securityerrors.js]
+skip-if = buildapp == 'mulet'
+[browser_webconsole_bug_846918_hsts_invalid-headers.js]
+skip-if = buildapp == 'mulet' || e10s # Bug 1042253 - webconsole e10s tests
+[browser_webconsole_bug_915141_toggle_response_logging_with_keyboard.js]
+[browser_webconsole_filter_buttons_contextmenu.js]
+[browser_webconsole_bug_1006027_message_timestamps_incorrect.js]
+skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug intermittent)
+[browser_webconsole_bug_1010953_cspro.js]
+[browser_webconsole_certificate_messages.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_show_subresource_security_errors.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_cached_autocomplete.js]
+[browser_webconsole_change_font_size.js]
+[browser_webconsole_chrome.js]
+[browser_webconsole_clickable_urls.js]
+skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
+[browser_webconsole_closure_inspection.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_completion.js]
+[browser_webconsole_console_extras.js]
+[browser_webconsole_console_logging_api.js]
+[browser_webconsole_count.js]
+[browser_webconsole_dont_navigate_on_doubleclick.js]
+[browser_webconsole_execution_scope.js]
+[browser_webconsole_for_of.js]
+[browser_webconsole_history.js]
+[browser_webconsole_input_field_focus_on_panel_select.js]
+[browser_webconsole_inspect-parsed-documents.js]
+[browser_webconsole_js_input_expansion.js]
+[browser_webconsole_jsterm.js]
+skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
+[browser_webconsole_live_filtering_of_message_types.js]
+[browser_webconsole_live_filtering_on_search_strings.js]
+[browser_webconsole_message_node_id.js]
+[browser_webconsole_netlogging.js]
+[browser_webconsole_network_panel.js]
+[browser_webconsole_notifications.js]
+[browser_webconsole_open-links-without-callback.js]
+[browser_webconsole_output_copy_newlines.js]
+[browser_webconsole_output_order.js]
+[browser_webconsole_property_provider.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_scratchpad_panel_link.js]
+[browser_webconsole_split.js]
+[browser_webconsole_split_escape_key.js]
+[browser_webconsole_split_focus.js]
+[browser_webconsole_split_persist.js]
+skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
+[browser_webconsole_view_source.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s (expectUncaughtException)
+[browser_webconsole_reflow.js]
+[browser_webconsole_log_file_filter.js]
+[browser_webconsole_expandable_timestamps.js]
+[browser_webconsole_autocomplete_in_debugger_stackframe.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_autocomplete_popup_close_on_tab_switch.js]
+skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
+[browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js]
+[browser_console_hide_jsterm_when_devtools_chrome_enabled_false.js]
+[browser_webconsole_output_01.js]
+skip-if = e10s # Bug 1042253 - webconsole e10s tests
+[browser_webconsole_output_02.js]
+[browser_webconsole_output_03.js]
+[browser_webconsole_output_04.js]
+[browser_webconsole_output_05.js]
+[browser_webconsole_output_06.js]
+[browser_webconsole_output_dom_elements_01.js]
+[browser_webconsole_output_dom_elements_02.js]
+skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
+[browser_webconsole_output_dom_elements_03.js]
+[browser_webconsole_output_dom_elements_04.js]
+skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug timeout)
+[browser_webconsole_output_events.js]
+[browser_webconsole_output_table.js]
+[browser_console_variables_view_highlighter.js]
+[browser_webconsole_start_netmon_first.js]
+[browser_webconsole_console_trace_duplicates.js]
+[browser_webconsole_cd_iframe.js]
+[browser_webconsole_autocomplete_crossdomain_iframe.js]
+[browser_webconsole_console_custom_styles.js]
+[browser_webconsole_console_api_stackframe.js]
+[browser_webconsole_column_numbers.js]
+[browser_console_open_or_focus.js]
diff --git a/browser/devtools/webconsole/test/browser_bug1045902_console_csp_ignore_reflected_xss_message.js b/browser/devtools/webconsole/test/browser_bug1045902_console_csp_ignore_reflected_xss_message.js
new file mode 100644
index 000000000..27cbf8ad0
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_bug1045902_console_csp_ignore_reflected_xss_message.js
@@ -0,0 +1,59 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Description of the test:
+ * We are loading a file with the following CSP:
+ * 'reflected-xss filter'
+ * This directive is not supported, hence we confirm that
+ * the according message is displayed in the web console.
+ */
+
+const EXPECTED_RESULT = "Not supporting directive 'reflected-xss'. Directive and values will be ignored.";
+const TEST_FILE = "http://example.com/browser/browser/devtools/webconsole/test/" +
+ "test_bug1045902_console_csp_ignore_reflected_xss_message.html";
+
+let hud = undefined;
+
+let TEST_URI = "data:text/html;charset=utf8,Web Console CSP ignoring reflected XSS (bug 1045902)";
+
+let test = asyncTest(function* () {
+ let { browser } = yield loadTab(TEST_URI);
+
+ hud = yield openConsole();
+
+ yield loadDocument(browser);
+ yield testViolationMessage();
+
+ hud = null;
+});
+
+
+function loadDocument(browser) {
+ let deferred = promise.defer();
+
+ hud.jsterm.clearOutput()
+ browser.addEventListener("load", function onLoad() {
+ browser.removeEventListener("load", onLoad, true);
+ deferred.resolve();
+ }, true);
+ content.location = TEST_FILE;
+
+ return deferred.promise;
+}
+
+function testViolationMessage() {
+ let deferred = promise.defer();
+ let aOutputNode = hud.outputNode;
+
+ return waitForSuccess({
+ name: "Confirming that CSP logs messages to the console when 'reflected-xss' directive is used!",
+ validator: function() {
+ console.log(hud.outputNode.textContent);
+ let success = false;
+ success = hud.outputNode.textContent.indexOf(EXPECTED_RESULT) > -1;
+ return success;
+ }
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_bug664688_sandbox_update_after_navigation.js b/browser/devtools/webconsole/test/browser_bug664688_sandbox_update_after_navigation.js
new file mode 100644
index 000000000..2203e2209
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_bug664688_sandbox_update_after_navigation.js
@@ -0,0 +1,91 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests if the JSTerm sandbox is updated when the user navigates from one
+// domain to another, in order to avoid permission denied errors with a sandbox
+// created for a different origin.
+
+"use strict";
+
+let test = asyncTest(function* () {
+ const TEST_URI1 = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+ const TEST_URI2 = "http://example.org/browser/browser/devtools/webconsole/test/test-console.html";
+
+ yield loadTab(TEST_URI1);
+ let hud = yield openConsole();
+
+ hud.jsterm.clearOutput();
+ hud.jsterm.execute("window.location.href");
+
+ info("wait for window.location.href");
+
+ let msgForLocation1 = {
+ webconsole: hud,
+ messages: [
+ {
+ name: "window.location.href jsterm input",
+ text: "window.location.href",
+ category: CATEGORY_INPUT,
+ },
+ {
+ name: "window.location.href result is displayed",
+ text: TEST_URI1,
+ category: CATEGORY_OUTPUT,
+ },
+ ],
+ };
+
+ yield waitForMessages(msgForLocation1);
+
+ // load second url
+ content.location = TEST_URI2;
+ yield loadBrowser(gBrowser.selectedBrowser);
+
+ is(hud.outputNode.textContent.indexOf("Permission denied"), -1,
+ "no permission denied errors");
+
+ hud.jsterm.clearOutput();
+ hud.jsterm.execute("window.location.href");
+
+ info("wait for window.location.href after page navigation");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "window.location.href jsterm input",
+ text: "window.location.href",
+ category: CATEGORY_INPUT,
+ },
+ {
+ name: "window.location.href result is displayed",
+ text: TEST_URI2,
+ category: CATEGORY_OUTPUT,
+ },
+ ],
+ });
+
+ is(hud.outputNode.textContent.indexOf("Permission denied"), -1,
+ "no permission denied errors");
+
+ gBrowser.goBack();
+
+ yield waitForSuccess({
+ name: "go back",
+ validator: function() {
+ return content.location.href == TEST_URI1;
+ },
+ });
+
+ hud.jsterm.clearOutput();
+ executeSoon(() => {
+ hud.jsterm.execute("window.location.href");
+ });
+
+ info("wait for window.location.href after goBack()");
+ yield waitForMessages(msgForLocation1);
+ is(hud.outputNode.textContent.indexOf("Permission denied"), -1,
+ "no permission denied errors");
+});
diff --git a/browser/devtools/webconsole/test/browser_bug_638949_copy_link_location.js b/browser/devtools/webconsole/test/browser_bug_638949_copy_link_location.js
new file mode 100644
index 000000000..40ae04394
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_bug_638949_copy_link_location.js
@@ -0,0 +1,105 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 for the "Copy link location" context menu item shown when you right
+// click network requests in the output.
+
+"use strict";
+
+let test = asyncTest(function* () {
+ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" +
+ "test/test-console.html?_date=" + Date.now();
+ const COMMAND_NAME = "consoleCmd_copyURL";
+ const CONTEXT_MENU_ID = "#menu_copyURL";
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("devtools.webconsole.filter.networkinfo");
+ });
+
+ Services.prefs.setBoolPref("devtools.webconsole.filter.networkinfo", true);
+
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ let output = hud.outputNode;
+ let menu = hud.iframeWindow.document.getElementById("output-contextmenu");
+
+ hud.jsterm.clearOutput();
+ content.console.log("bug 638949");
+
+ // Test that the "Copy Link Location" command is disabled for non-network
+ // messages.
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "bug 638949",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ output.focus();
+ let message = [...result.matched][0];
+
+ goUpdateCommand(COMMAND_NAME);
+ ok(!isEnabled(), COMMAND_NAME + " is disabled");
+
+ // Test that the "Copy Link Location" menu item is hidden for non-network
+ // messages.
+ message.scrollIntoView();
+
+ yield waitForContextMenu(menu, message, () => {
+ let isHidden = menu.querySelector(CONTEXT_MENU_ID).hidden;
+ ok(isHidden, CONTEXT_MENU_ID + " is hidden");
+ });
+
+ hud.jsterm.clearOutput();
+ content.location.reload(); // Reloading will produce network logging
+
+ // Test that the "Copy Link Location" command is enabled and works
+ // as expected for any network-related message.
+ // This command should copy only the URL.
+ [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "test-console.html",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ output.focus();
+ message = [...result.matched][0];
+ hud.ui.output.selectMessage(message);
+
+ goUpdateCommand(COMMAND_NAME);
+ ok(isEnabled(), COMMAND_NAME + " is enabled");
+
+ info("expected clipboard value: " + message.url);
+
+ let deferred = promise.defer();
+
+ waitForClipboard((aData) => { return aData.trim() == message.url; },
+ () => { goDoCommand(COMMAND_NAME); },
+ () => { deferred.resolve(null); },
+ () => { deferred.reject(null); });
+
+ yield deferred.promise;
+
+ // Test that the "Copy Link Location" menu item is visible for network-related
+ // messages.
+ message.scrollIntoView();
+
+ yield waitForContextMenu(menu, message, () => {
+ let isVisible = !menu.querySelector(CONTEXT_MENU_ID).hidden;
+ ok(isVisible, CONTEXT_MENU_ID + " is visible");
+ });
+
+ // Return whether "Copy Link Location" command is enabled or not.
+ function isEnabled() {
+ let controller = top.document.commandDispatcher
+ .getControllerForCommand(COMMAND_NAME);
+ return controller && controller.isCommandEnabled(COMMAND_NAME);
+ }
+});
diff --git a/browser/devtools/webconsole/test/browser_bug_862916_console_dir_and_filter_off.js b/browser/devtools/webconsole/test/browser_bug_862916_console_dir_and_filter_off.js
new file mode 100644
index 000000000..676fd5a93
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_bug_862916_console_dir_and_filter_off.js
@@ -0,0 +1,31 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that the output for console.dir() works even if Logging filter is off.
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf8,<p>test for bug 862916";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+
+ ok(hud, "web console opened");
+
+ hud.setFilterState("log", false);
+ registerCleanupFunction(() => hud.setFilterState("log", true));
+
+ hud.jsterm.execute("window.fooBarz = 'bug862916'; " +
+ "console.dir(window)");
+
+ let varView = yield hud.jsterm.once("variablesview-fetched");
+ ok(varView, "variables view object");
+
+ yield findVariableViewProperties(varView, [
+ { name: "fooBarz", value: "bug862916" },
+ ], { webconsole: hud });
+});
+
diff --git a/browser/devtools/webconsole/test/browser_bug_865288_repeat_different_objects.js b/browser/devtools/webconsole/test/browser_bug_865288_repeat_different_objects.js
new file mode 100644
index 000000000..a3e3a8abe
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_bug_865288_repeat_different_objects.js
@@ -0,0 +1,63 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that makes sure messages are not considered repeated when console.log()
+// is invoked with different objects, see bug 865288.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-repeated-messages.html";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+
+ info("waiting for 3 console.log objects");
+
+ hud.jsterm.clearOutput(true);
+ hud.jsterm.execute("window.testConsoleObjects()");
+
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "3 console.log messages",
+ text: "abba",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ count: 3,
+ repeats: 1,
+ objects: true,
+ }],
+ });
+
+ let msgs = [...result.matched];
+ is(msgs.length, 3, "3 message elements");
+
+ for (let i = 0; i < msgs.length; i++) {
+ info("test message element #" + i);
+
+ let msg = msgs[i];
+ let clickable = msg.querySelector(".message-body a");
+ ok(clickable, "clickable object #" + i);
+
+ msg.scrollIntoView(false);
+ yield clickObject(clickable, i);
+ }
+
+ function* clickObject(obj, i)
+ {
+ executeSoon(() => {
+ EventUtils.synthesizeMouse(obj, 2, 2, {}, hud.iframeWindow);
+ });
+
+ let varView = yield hud.jsterm.once("variablesview-fetched");
+ ok(varView, "variables view fetched #" + i);
+
+ yield findVariableViewProperties(varView, [
+ { name: "id", value: "abba" + i },
+ ], { webconsole: hud });
+ }
+});
+
diff --git a/browser/devtools/webconsole/test/browser_bug_865871_variables_view_close_on_esc_key.js b/browser/devtools/webconsole/test/browser_bug_865871_variables_view_close_on_esc_key.js
new file mode 100644
index 000000000..a4dd76dbe
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_bug_865871_variables_view_close_on_esc_key.js
@@ -0,0 +1,98 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that the variables view sidebar can be closed by pressing Escape in the
+// web console.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-eval-in-stackframe.html";
+
+function test()
+{
+ let hud;
+
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ let {tab} = yield loadTab(TEST_URI);
+ hud = yield openConsole(tab);
+ let jsterm = hud.jsterm;
+
+ let msg = yield execute("fooObj");
+ ok(msg, "output message found");
+
+ let anchor = msg.querySelector("a");
+ let body = msg.querySelector(".message-body");
+ ok(anchor, "object anchor");
+ ok(body, "message body");
+ ok(body.textContent.contains('testProp: "testValue"'), "message text check");
+
+ msg.scrollIntoView();
+ executeSoon(() => {
+ EventUtils.synthesizeMouse(anchor, 2, 2, {}, hud.iframeWindow);
+ });
+
+ let vviewVar = yield jsterm.once("variablesview-fetched");
+ let vview = vviewVar._variablesView;
+ ok(vview, "variables view object");
+
+ let [result] = yield findVariableViewProperties(vviewVar, [
+ { name: "testProp", value: "testValue" },
+ ], { webconsole: hud });
+
+ let prop = result.matchedProp;
+ ok(prop, "matched the |testProp| property in the variables view");
+
+ vview.window.focus();
+
+ executeSoon(() => {
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ });
+ yield jsterm.once("sidebar-closed");
+
+ jsterm.clearOutput();
+
+ msg = yield execute("window.location");
+ ok(msg, "output message found");
+
+ body = msg.querySelector(".message-body");
+ ok(body, "message body");
+ anchor = msg.querySelector("a");
+ ok(anchor, "object anchor");
+ ok(body.textContent.contains("Location \u2192 http://example.com/browser/"),
+ "message text check");
+
+ msg.scrollIntoView();
+ executeSoon(() => {
+ EventUtils.synthesizeMouse(anchor, 2, 2, {}, hud.iframeWindow)
+ });
+ vviewVar = yield jsterm.once("variablesview-fetched");
+
+ vview = vviewVar._variablesView;
+ ok(vview, "variables view object");
+
+ yield findVariableViewProperties(vviewVar, [
+ { name: "host", value: "example.com" },
+ ], { webconsole: hud });
+
+ vview.window.focus();
+
+ msg.scrollIntoView();
+ executeSoon(() => {
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ });
+
+ yield jsterm.once("sidebar-closed");
+ }
+
+ function execute(str) {
+ let deferred = promise.defer();
+ hud.jsterm.execute(str, (msg) => {
+ deferred.resolve(msg);
+ });
+ return deferred.promise;
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_bug_869003_inspect_cross_domain_object.js b/browser/devtools/webconsole/test/browser_bug_869003_inspect_cross_domain_object.js
new file mode 100644
index 000000000..8a5ad49af
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_bug_869003_inspect_cross_domain_object.js
@@ -0,0 +1,76 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that users can inspect objects logged from cross-domain iframes -
+// bug 869003.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-869003-top-window.html";
+
+let test = asyncTest(function* () {
+ // This test is slightly more involved: it opens the web console, then the
+ // variables view for a given object, it updates a property in the view and
+ // checks the result. We can get a timeout with debug builds on slower machines.
+ requestLongerTimeout(2);
+
+ yield loadTab("data:text/html;charset=utf8,<p>hello");
+ let hud = yield openConsole();
+
+ content.location = TEST_URI;
+
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "console.log message",
+ text: "foobar",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ objects: true,
+ }],
+ });
+
+ let msg = [...result.matched][0];
+ ok(msg, "message element");
+
+ let body = msg.querySelector(".message-body");
+ ok(body, "message body");
+
+ let clickable = result.clickableElements[0];
+ ok(clickable, "clickable object found");
+ ok(body.textContent.contains('{ hello: "world!",'), "message text check");
+
+ executeSoon(() => {
+ EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow)
+ });
+
+ let aVar = yield hud.jsterm.once("variablesview-fetched");
+ ok(aVar, "variables view fetched");
+ ok(aVar._variablesView, "variables view object");
+
+ [result] = yield findVariableViewProperties(aVar, [
+ { name: "hello", value: "world!" },
+ { name: "bug", value: 869003 },
+ ], { webconsole: hud });
+
+ let prop = result.matchedProp;
+ ok(prop, "matched the |hello| property in the variables view");
+
+ // Check that property value updates work.
+ aVar = yield updateVariablesViewProperty({
+ property: prop,
+ field: "value",
+ string: "'omgtest'",
+ webconsole: hud,
+ });
+
+ info("onFetchAfterUpdate");
+
+ yield findVariableViewProperties(aVar, [
+ { name: "hello", value: "omgtest" },
+ { name: "bug", value: 869003 },
+ ], { webconsole: hud });
+});
+
diff --git a/browser/devtools/webconsole/test/browser_bug_871156_ctrlw_close_tab.js b/browser/devtools/webconsole/test/browser_bug_871156_ctrlw_close_tab.js
new file mode 100644
index 000000000..503103566
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_bug_871156_ctrlw_close_tab.js
@@ -0,0 +1,79 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that Ctrl-W closes the Browser Console and that Ctrl-W closes the
+// current tab when using the Web Console - bug 871156.
+
+"use strict";
+
+let test = asyncTest(function* () {
+ const TEST_URI = "data:text/html;charset=utf8,<title>bug871156</title>\n" +
+ "<p>hello world";
+ let firstTab = gBrowser.selectedTab;
+
+ Services.prefs.setBoolPref("browser.tabs.animate", false);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.tabs.animate");
+ });
+
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+ ok(hud, "Web Console opened");
+
+ let tabClosed = promise.defer();
+ let toolboxDestroyed = promise.defer();
+ let tabSelected = promise.defer();
+
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ let toolbox = gDevTools.getToolbox(target);
+
+ gBrowser.tabContainer.addEventListener("TabClose", function onTabClose() {
+ gBrowser.tabContainer.removeEventListener("TabClose", onTabClose);
+ info("tab closed");
+ tabClosed.resolve(null);
+ });
+
+ gBrowser.tabContainer.addEventListener("TabSelect", function onTabSelect() {
+ gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect);
+ if (gBrowser.selectedTab == firstTab) {
+ info("tab selected");
+ tabSelected.resolve(null);
+ }
+ });
+
+ toolbox.once("destroyed", () => {
+ info("toolbox destroyed");
+ toolboxDestroyed.resolve(null);
+ });
+
+ // Get out of the web console initialization.
+ executeSoon(() => {
+ EventUtils.synthesizeKey("w", { accelKey: true });
+ });
+
+
+ yield promise.all([tabClosed.promise, toolboxDestroyed.promise,
+ tabSelected.promise]);
+ info("promise.all resolved. start testing the Browser Console");
+
+ hud = yield HUDService.toggleBrowserConsole();
+ ok(hud, "Browser Console opened");
+
+ let deferred = promise.defer();
+
+ Services.obs.addObserver(function onDestroy() {
+ Services.obs.removeObserver(onDestroy, "web-console-destroyed");
+ ok(true, "the Browser Console closed");
+
+ deferred.resolve(null);
+ }, "web-console-destroyed", false);
+
+ waitForFocus(() => {
+ EventUtils.synthesizeKey("w", { accelKey: true }, hud.iframeWindow);
+ }, hud.iframeWindow);
+
+ yield deferred.promise;
+});
diff --git a/browser/devtools/webconsole/test/browser_cached_messages.js b/browser/devtools/webconsole/test/browser_cached_messages.js
new file mode 100644
index 000000000..a9a98ac17
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_cached_messages.js
@@ -0,0 +1,53 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 to see if the cached messages are displayed when the console UI is opened.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-webconsole-error-observer.html";
+
+function test()
+{
+ waitForExplicitFinish();
+
+ expectUncaughtException();
+
+ loadTab(TEST_URI).then(testOpenUI);
+}
+
+function testOpenUI(aTestReopen)
+{
+ openConsole().then((hud) => {
+ waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ text: "log Bazzle",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "error Bazzle",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_ERROR,
+ },
+ {
+ text: "bazBug611032",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ },
+ {
+ text: "cssColorBug611032",
+ category: CATEGORY_CSS,
+ severity: SEVERITY_WARNING,
+ },
+ ],
+ }).then(() => {
+ closeConsole(gBrowser.selectedTab).then(() => {
+ aTestReopen && info("will reopen the Web Console");
+ executeSoon(aTestReopen ? testOpenUI : finishTest);
+ });
+ });
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_console.js b/browser/devtools/webconsole/test/browser_console.js
new file mode 100644
index 000000000..df971ec0f
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console.js
@@ -0,0 +1,120 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test the basic features of the Browser Console, bug 587757.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html?" + Date.now();
+
+const TEST_XHR_ERROR_URI = `http://example.com/404.html?${Date.now()}`;
+
+"use strict";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+
+ let opened = waitForConsole();
+
+ let hud = HUDService.getBrowserConsole();
+ ok(!hud, "browser console is not open");
+ info("wait for the browser console to open with ctrl-shift-j");
+ EventUtils.synthesizeKey("j", { accelKey: true, shiftKey: true }, window);
+
+ hud = yield opened;
+ ok(hud, "browser console opened");
+
+ yield consoleOpened(hud);
+});
+
+function consoleOpened(hud)
+{
+ hud.jsterm.clearOutput(true);
+
+ expectUncaughtException();
+ executeSoon(() => {
+ foobarExceptionBug587757();
+ });
+
+ // Add a message from a chrome window.
+ hud.iframeWindow.console.log("bug587757a");
+
+ // Add a message from a content window.
+ content.console.log("bug587757b");
+
+ // Test eval.
+ hud.jsterm.execute("document.location.href");
+
+ // Check for network requests.
+ let xhr = new XMLHttpRequest();
+ xhr.onload = () => console.log("xhr loaded, status is: " + xhr.status);
+ xhr.open("get", TEST_URI, true);
+ xhr.send();
+
+ // Check for xhr error.
+ let xhrErr = new XMLHttpRequest();
+ xhrErr.onload = () => console.log("xhr error loaded, status is: " + xhrErr.status);
+ xhrErr.open("get", TEST_XHR_ERROR_URI, true);
+ xhrErr.send();
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "chrome window console.log() is displayed",
+ text: "bug587757a",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ },
+ {
+ name: "content window console.log() is displayed",
+ text: "bug587757b",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ },
+ {
+ name: "jsterm eval result",
+ text: "browser.xul",
+ category: CATEGORY_OUTPUT,
+ severity: SEVERITY_LOG,
+ },
+ {
+ name: "exception message",
+ text: "foobarExceptionBug587757",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ },
+ {
+ name: "network message",
+ text: "test-console.html",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_INFO,
+ isXhr: true,
+ },
+ {
+ name: "xhr error message",
+ text: "404.html",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_ERROR,
+ isXhr: true,
+ },
+ ],
+ });
+}
+
+function waitForConsole() {
+ let deferred = promise.defer();
+
+ Services.obs.addObserver(function observer(aSubject) {
+ Services.obs.removeObserver(observer, "web-console-created");
+ aSubject.QueryInterface(Ci.nsISupportsString);
+
+ let hud = HUDService.getBrowserConsole();
+ ok(hud, "browser console is open");
+ is(aSubject.data, hud.hudId, "notification hudId is correct");
+
+ executeSoon(() => deferred.resolve(hud));
+ }, "web-console-created", false);
+
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js b/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js
new file mode 100644
index 000000000..bc485ebb1
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_addonsdk_loader_exception.js
@@ -0,0 +1,90 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that exceptions from scripts loaded with the addon-sdk loader are
+// opened correctly in View Source from the Browser Console.
+// See bug 866950.
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf8,<p>hello world from bug 866950";
+
+function test()
+{
+ requestLongerTimeout(2);
+
+ let webconsole, browserconsole;
+
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ let {tab} = yield loadTab(TEST_URI);
+ webconsole = yield openConsole(tab);
+ ok(webconsole, "web console opened");
+
+ browserconsole = yield HUDService.toggleBrowserConsole();
+ ok(browserconsole, "browser console opened");
+
+ // Cause an exception in a script loaded with the addon-sdk loader.
+ let toolbox = gDevTools.getToolbox(webconsole.target);
+ let oldPanels = toolbox._toolPanels;
+ toolbox._toolPanels = {}; // non-iterable
+
+ function fixToolbox() {
+ toolbox._toolPanels = oldPanels;
+ }
+
+ info("generate exception and wait for message");
+
+ executeSoon(() => {
+ executeSoon(fixToolbox);
+ expectUncaughtException();
+ toolbox.getToolPanels();
+ });
+
+ let [result] = yield waitForMessages({
+ webconsole: browserconsole,
+ messages: [{
+ text: "TypeError: this._toolPanels is not iterable",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ }],
+ });
+
+ fixToolbox();
+
+ let msg = [...result.matched][0];
+ ok(msg, "message element found");
+ let locationNode = msg.querySelector(".message-location");
+ ok(locationNode, "message location element found");
+
+ let title = locationNode.getAttribute("title");
+ info("location node title: " + title);
+ isnot(title.indexOf(" -> "), -1, "error comes from a subscript");
+
+ let viewSource = browserconsole.viewSource;
+ let URL = null;
+ let clickPromise = promise.defer();
+ browserconsole.viewSource = (aURL) => {
+ info("browserconsole.viewSource() was invoked: " + aURL);
+ URL = aURL;
+ clickPromise.resolve(null);
+ };
+
+ msg.scrollIntoView();
+ EventUtils.synthesizeMouse(locationNode, 2, 2, {},
+ browserconsole.iframeWindow);
+
+ info("wait for click on locationNode");
+ yield clickPromise;
+
+ info("view-source url: " + URL);
+ ok(URL, "we have some source URL after the click");
+ isnot(URL.indexOf("toolbox.js"), -1, "we have the expected view source URL");
+ is(URL.indexOf("->"), -1, "no -> in the URL given to view-source");
+
+ browserconsole.viewSource = viewSource;
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_console_clear_on_reload.js b/browser/devtools/webconsole/test/browser_console_clear_on_reload.js
new file mode 100644
index 000000000..c88fdc7d5
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_clear_on_reload.js
@@ -0,0 +1,54 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that clear output on page reload works - bug 705921.
+
+"use strict";
+
+let test = asyncTest(function*() {
+ const PREF = "devtools.webconsole.persistlog";
+ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+ Services.prefs.setBoolPref(PREF, false);
+ registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
+
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+ ok(hud, "Web Console opened");
+
+ hud.jsterm.clearOutput();
+ hud.jsterm.execute("console.log('foobarz1')");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "foobarz1",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ BrowserReload();
+ yield loadBrowser(gBrowser.selectedBrowser);
+
+ hud.jsterm.execute("console.log('foobarz2')");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "test-console.html",
+ category: CATEGORY_NETWORK,
+ },
+ {
+ text: "foobarz2",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ is(hud.outputNode.textContent.indexOf("foobarz1"), -1,
+ "foobarz1 has been removed from output");
+});
diff --git a/browser/devtools/webconsole/test/browser_console_click_focus.js b/browser/devtools/webconsole/test/browser_console_click_focus.js
new file mode 100644
index 000000000..30a18598c
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_click_focus.js
@@ -0,0 +1,55 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that the input field is focused when the console is opened.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "Dolske Digs Bacon",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ let msg = [...result.matched][0];
+ let outputItem = msg.querySelector(".message-body");
+ ok(outputItem, "found a logged message");
+
+ let inputNode = hud.jsterm.inputNode;
+ ok(inputNode.getAttribute("focused"), "input node is focused, first");
+
+ let lostFocus = () => {
+ inputNode.removeEventListener("blur", lostFocus);
+ info("input node lost focus");
+ }
+
+ inputNode.addEventListener("blur", lostFocus);
+
+ document.getElementById("urlbar").click();
+
+ ok(!inputNode.getAttribute("focused"), "input node is not focused");
+
+ EventUtils.sendMouseEvent({type: "click"}, hud.outputNode);
+
+ ok(inputNode.getAttribute("focused"), "input node is focused, second time")
+
+ // test click-drags are not focusing the input element.
+ EventUtils.sendMouseEvent({type: "mousedown", clientX: 3, clientY: 4},
+ outputItem);
+ EventUtils.sendMouseEvent({type: "click", clientX: 15, clientY: 5},
+ outputItem);
+
+ todo(!inputNode.getAttribute("focused"), "input node is not focused after drag");
+});
+
diff --git a/browser/devtools/webconsole/test/browser_console_consolejsm_output.js b/browser/devtools/webconsole/test/browser_console_consolejsm_output.js
new file mode 100644
index 000000000..0601c11a4
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_consolejsm_output.js
@@ -0,0 +1,137 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that Console.jsm outputs messages to the Browser Console, bug 851231.
+
+"use strict";
+
+let test = asyncTest(function*() {
+ let storage = Cc["@mozilla.org/consoleAPI-storage;1"].getService(Ci.nsIConsoleAPIStorage);
+ storage.clearEvents();
+
+ let console = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).console;
+ console.log("bug861338-log-cached");
+
+ let hud = yield HUDService.toggleBrowserConsole();
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "cached console.log message",
+ text: "bug861338-log-cached",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ hud.jsterm.clearOutput(true);
+
+ function testTrace() {
+ console.trace();
+ }
+
+ console.time("foobarTimer");
+ let foobar = { bug851231prop: "bug851231value" };
+
+ console.log("bug851231-log");
+ console.info("bug851231-info");
+ console.warn("bug851231-warn");
+ console.error("bug851231-error", foobar);
+ console.debug("bug851231-debug");
+ console.dir(document);
+ testTrace();
+ console.timeEnd("foobarTimer");
+
+ info("wait for the Console.jsm messages");
+
+ let results = yield waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "console.log output",
+ text: "bug851231-log",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ },
+ {
+ name: "console.info output",
+ text: "bug851231-info",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_INFO,
+ },
+ {
+ name: "console.warn output",
+ text: "bug851231-warn",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_WARNING,
+ },
+ {
+ name: "console.error output",
+ text: /\bbug851231-error\b.+\{\s*bug851231prop:\s"bug851231value"\s*\}/,
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_ERROR,
+ objects: true,
+ },
+ {
+ name: "console.debug output",
+ text: "bug851231-debug",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ },
+ {
+ name: "console.trace output",
+ consoleTrace: {
+ file: "browser_console_consolejsm_output.js",
+ fn: "testTrace",
+ },
+ },
+ {
+ name: "console.dir output",
+ consoleDir: /XULDocument\s+.+\s+chrome:\/\/.+\/browser\.xul/,
+ },
+ {
+ name: "console.time output",
+ consoleTime: "foobarTimer",
+ },
+ {
+ name: "console.timeEnd output",
+ consoleTimeEnd: "foobarTimer",
+ },
+ ],
+ });
+
+ let consoleErrorMsg = results[3];
+ ok(consoleErrorMsg, "console.error message element found");
+ let clickable = consoleErrorMsg.clickableElements[0];
+ ok(clickable, "clickable object found for console.error");
+
+ let deferred = promise.defer();
+
+ let onFetch = (aEvent, aVar) => {
+ // Skip the notification from console.dir variablesview-fetched.
+ if (aVar._variablesView != hud.jsterm._variablesView) {
+ return;
+ }
+ hud.jsterm.off("variablesview-fetched", onFetch);
+
+ deferred.resolve(aVar);
+ };
+
+ hud.jsterm.on("variablesview-fetched", onFetch);
+
+ clickable.scrollIntoView(false);
+
+ info("wait for variablesview-fetched");
+ executeSoon(() =>
+ EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow));
+
+ let varView = yield deferred.promise;
+ ok(varView, "object inspector opened on click");
+
+ yield findVariableViewProperties(varView, [{
+ name: "bug851231prop",
+ value: "bug851231value",
+ }], { webconsole: hud });
+});
diff --git a/browser/devtools/webconsole/test/browser_console_copy_command.js b/browser/devtools/webconsole/test/browser_console_copy_command.js
new file mode 100644
index 000000000..711dfb5b5
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_copy_command.js
@@ -0,0 +1,70 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+// Tests that the `copy` console helper works as intended.
+
+let gWebConsole, gJSTerm;
+
+let TEXT = "Lorem ipsum dolor sit amet, consectetur adipisicing " +
+ "elit, sed do eiusmod tempor incididunt ut labore et dolore magna " +
+ "aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
+ "laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
+ "dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
+ "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " +
+ "proident, sunt in culpa qui officia deserunt mollit anim id est laborum." +
+ new Date();
+
+let ID = "select-me";
+
+add_task(function* init() {
+ yield loadTab("data:text/html;charset=utf-8," +
+ "<body>" +
+ " <div>" +
+ " <h1>Testing copy command</h1>" +
+ " <p>This is some example text</p>" +
+ " <p id='select-me'>"+TEXT+"</p>" +
+ " </div>" +
+ " <div><p></p></div>" +
+ "</body>");
+
+ gWebConsole = yield openConsole();
+ gJSTerm = gWebConsole.jsterm;
+});
+
+add_task(function* test_copy() {
+ let RANDOM = Math.random();
+ let string = "Text: " + RANDOM;
+ let obj = {a: 1, b: "foo", c: RANDOM};
+
+ let samples = [[RANDOM, RANDOM],
+ [JSON.stringify(string), string],
+ [obj.toSource(), JSON.stringify(obj, null, " ")],
+ ["$('#" + ID + "')", content.document.getElementById(ID).outerHTML]
+ ];
+ for (let [source, reference] of samples) {
+ let deferredResult = promise.defer();
+
+ SimpleTest.waitForClipboard(
+ "" + reference,
+ () => {
+ let command = "copy(" + source + ")";
+ info("Attempting to copy: " + source);
+ info("Executing command: " + command);
+ gJSTerm.execute(command, msg => {
+ is(msg, undefined, "Command success: " + command);
+ });
+ },
+ deferredResult.resolve,
+ deferredResult.reject);
+
+ yield deferredResult.promise;
+ }
+});
+
+add_task(function* cleanup() {
+ gWebConsole = gJSTerm = null;
+ gBrowser.removeTab(gBrowser.selectedTab);
+ finishTest();
+});
diff --git a/browser/devtools/webconsole/test/browser_console_copy_entire_message_context_menu.js b/browser/devtools/webconsole/test/browser_console_copy_entire_message_context_menu.js
new file mode 100644
index 000000000..937793672
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_copy_entire_message_context_menu.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test copying of the entire console message when right-clicked
+// with no other text selected. See Bug 1100562.
+
+function test() {
+ let hud;
+ let outputNode;
+ let contextMenu;
+
+ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+ const TEST_FILE = TEST_URI.substr(TEST_URI.lastIndexOf("/"));
+
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ const {tab} = yield loadTab(TEST_URI);
+ hud = yield openConsole(tab);
+ outputNode = hud.outputNode;
+ contextMenu = hud.iframeWindow.document.getElementById("output-contextmenu");
+
+ registerCleanupFunction(() => {
+ hud = outputNode = contextMenu = null;
+ });
+
+ hud.jsterm.clearOutput();
+ content.console.log("bug 1100562");
+
+ let [results] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "bug 1100562",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }]
+ });
+
+ outputNode.focus();
+ let message = [...results.matched][0];
+ message.scrollIntoView();
+
+ yield waitForContextMenu(contextMenu, message, copyFromPopup, testContextMenuCopy);
+
+ function copyFromPopup() {
+ let copyItem = contextMenu.querySelector("#cMenu_copy");
+ copyItem.doCommand();
+
+ let controller = top.document.commandDispatcher.getControllerForCommand("cmd_copy");
+ is(controller.isCommandEnabled("cmd_copy"), true, "cmd_copy is enabled");
+ }
+
+ function testContextMenuCopy() {
+ waitForClipboard((str) => { return message.textContent.trim() == str.trim(); },
+ () => { goDoCommand("cmd_copy") },
+ () => {}, () => {}
+ );
+ }
+
+ yield closeConsole(tab);
+ }
+} \ No newline at end of file
diff --git a/browser/devtools/webconsole/test/browser_console_dead_objects.js b/browser/devtools/webconsole/test/browser_console_dead_objects.js
new file mode 100644
index 000000000..574573064
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_dead_objects.js
@@ -0,0 +1,86 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that Dead Objects do not break the Web/Browser Consoles. See bug 883649.
+// This test does:
+// - opens a new tab,
+// - opens the Browser Console,
+// - stores a reference to the content document of the tab on the chrome window object,
+// - closes the tab,
+// - tries to use the object that was pointing to the now-defunct content
+// document. This is the dead object.
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf8,<p>dead objects!";
+
+function test()
+{
+ let hud = null;
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("devtools.chrome.enabled");
+ });
+
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ Services.prefs.setBoolPref("devtools.chrome.enabled", true);
+ let {tab} = yield loadTab(TEST_URI);
+
+ info("open the browser console");
+
+ hud = yield HUDService.toggleBrowserConsole();
+ ok(hud, "browser console opened");
+
+ let jsterm = hud.jsterm;
+
+ jsterm.clearOutput();
+
+ // Add the reference to the content document.
+ yield jsterm.execute("Cu = Components.utils;" +
+ "Cu.import('resource://gre/modules/Services.jsm');" +
+ "chromeWindow = Services.wm.getMostRecentWindow('navigator:browser');" +
+ "foobarzTezt = chromeWindow.content.document;" +
+ "delete chromeWindow");
+
+ gBrowser.removeCurrentTab();
+
+ let msg = yield jsterm.execute("foobarzTezt");
+
+ isnot(hud.outputNode.textContent.indexOf("[object DeadObject]"), -1,
+ "dead object found");
+
+ jsterm.setInputValue("foobarzTezt");
+
+ for (let c of ".hello") {
+ EventUtils.synthesizeKey(c, {}, hud.iframeWindow);
+ }
+
+ yield jsterm.execute();
+
+ isnot(hud.outputNode.textContent.indexOf("can't access dead object"), -1,
+ "'cannot access dead object' message found");
+
+ // Click the second execute output.
+ let clickable = msg.querySelector("a");
+ ok(clickable, "clickable object found");
+ isnot(clickable.textContent.indexOf("[object DeadObject]"), -1,
+ "message text check");
+
+ msg.scrollIntoView();
+
+ executeSoon(() => {
+ EventUtils.synthesizeMouseAtCenter(clickable, {}, hud.iframeWindow);
+ });
+
+ yield jsterm.once("variablesview-fetched");
+ ok(true, "variables view fetched");
+
+ msg = yield jsterm.execute("delete window.foobarzTezt; 2013-26");
+
+ isnot(msg.textContent.indexOf("1987"), -1, "result message found");
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_console_error_source_click.js b/browser/devtools/webconsole/test/browser_console_error_source_click.js
new file mode 100644
index 000000000..5e2ecb977
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_error_source_click.js
@@ -0,0 +1,73 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that JS errors and CSS warnings open view source when their source link
+// is clicked in the Browser Console. See bug 877778.
+
+const TEST_URI = "data:text/html;charset=utf8,<p>hello world from bug 877778 " +
+ "<button onclick='foobar.explode()' " +
+ "style='test-color: green-please'>click!</button>";
+function test()
+{
+ let hud;
+
+ loadTab(TEST_URI).then(() => {
+ HUDService.toggleBrowserConsole().then(browserConsoleOpened);
+ });
+
+ function browserConsoleOpened(aHud)
+ {
+ hud = aHud;
+ ok(hud, "browser console opened");
+
+ let button = content.document.querySelector("button");
+ ok(button, "button element found");
+
+ info("generate exception and wait for the message");
+ executeSoon(() => {
+ expectUncaughtException();
+ button.click();
+ });
+
+ waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ text: "ReferenceError: foobar is not defined",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ },
+ {
+ text: "Unknown property 'test-color'",
+ category: CATEGORY_CSS,
+ severity: SEVERITY_WARNING,
+ },
+ ],
+ }).then(onMessageFound);
+ }
+
+ function onMessageFound(results)
+ {
+ let viewSource = hud.viewSource;
+ let viewSourceCalled = false;
+ hud.viewSource = () => viewSourceCalled = true;
+
+ for (let result of results) {
+ viewSourceCalled = false;
+
+ let msg = [...results[0].matched][0];
+ ok(msg, "message element found for: " + result.text);
+ let locationNode = msg.querySelector(".message-location");
+ ok(locationNode, "message location element found");
+
+ EventUtils.synthesizeMouse(locationNode, 2, 2, {}, hud.iframeWindow);
+
+ ok(viewSourceCalled, "view source opened");
+ }
+
+ hud.viewSource = viewSource;
+ finishTest();
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_console_filters.js b/browser/devtools/webconsole/test/browser_console_filters.js
new file mode 100644
index 000000000..1524b6bb1
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_filters.js
@@ -0,0 +1,60 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that the Browser Console does not use the same filter prefs as the Web
+// Console. See bug 878186.
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf8,<p>browser console filters";
+const WEB_CONSOLE_PREFIX = "devtools.webconsole.filter.";
+const BROWSER_CONSOLE_PREFIX = "devtools.browserconsole.filter.";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+
+ info("open the web console");
+ let hud = yield openConsole();
+ ok(hud, "web console opened");
+
+ is(Services.prefs.getBoolPref(BROWSER_CONSOLE_PREFIX + "exception"), true,
+ "'exception' filter is enabled (browser console)");
+ is(Services.prefs.getBoolPref(WEB_CONSOLE_PREFIX + "exception"), true,
+ "'exception' filter is enabled (web console)");
+
+ info("toggle 'exception' filter");
+ hud.setFilterState("exception", false);
+
+ is(Services.prefs.getBoolPref(BROWSER_CONSOLE_PREFIX + "exception"), true,
+ "'exception' filter is enabled (browser console)");
+ is(Services.prefs.getBoolPref(WEB_CONSOLE_PREFIX + "exception"), false,
+ "'exception' filter is disabled (web console)");
+
+ hud.setFilterState("exception", true);
+
+ // We need to let the console opening event loop to finish.
+ let deferred = promise.defer();
+ executeSoon(() => closeConsole().then(() => deferred.resolve(null)));
+ yield deferred.promise;
+
+ info("web console closed");
+ hud = yield HUDService.toggleBrowserConsole();
+ ok(hud, "browser console opened");
+
+ is(Services.prefs.getBoolPref(BROWSER_CONSOLE_PREFIX + "exception"), true,
+ "'exception' filter is enabled (browser console)");
+ is(Services.prefs.getBoolPref(WEB_CONSOLE_PREFIX + "exception"), true,
+ "'exception' filter is enabled (web console)");
+
+ info("toggle 'exception' filter");
+ hud.setFilterState("exception", false);
+
+ is(Services.prefs.getBoolPref(BROWSER_CONSOLE_PREFIX + "exception"), false,
+ "'exception' filter is disabled (browser console)");
+ is(Services.prefs.getBoolPref(WEB_CONSOLE_PREFIX + "exception"), true,
+ "'exception' filter is enabled (web console)");
+
+ hud.setFilterState("exception", true);
+});
diff --git a/browser/devtools/webconsole/test/browser_console_hide_jsterm_when_devtools_chrome_enabled_false.js b/browser/devtools/webconsole/test/browser_console_hide_jsterm_when_devtools_chrome_enabled_false.js
new file mode 100644
index 000000000..c4f4cd836
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_hide_jsterm_when_devtools_chrome_enabled_false.js
@@ -0,0 +1,105 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/*
+ * Bug 922161 - hide Browser Console JS input field if devtools.chrome.enabled is false
+ * when devtools.chrome.enabled then
+ * -browser console jsterm should be enabled
+ * -browser console object inspector properties should be set.
+ * -webconsole jsterm should be enabled
+ * -webconsole object inspector properties should be set.
+ *
+ * when devtools.chrome.enabled == false then
+ * -browser console jsterm should be disabled
+ * -browser console object inspector properties should not be set.
+ * -webconsole jsterm should be enabled
+ * -webconsole object inspector properties should be set.
+ */
+
+function testObjectInspectorPropertiesAreNotSet(variablesView) {
+ is(variablesView.eval, null, "vview.eval is null");
+ is(variablesView.switch, null, "vview.switch is null");
+ is(variablesView.delete, null, "vview.delete is null");
+}
+
+function* getVariablesView(hud) {
+ function openVariablesView(event, vview) {
+ deferred.resolve(vview._variablesView);
+ }
+
+ let deferred = promise.defer();
+ hud.jsterm.clearOutput();
+ hud.jsterm.execute('new Object()');
+
+ let [message] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "Object",
+ category: CATEGORY_OUTPUT,
+ }],
+ })
+
+ hud.jsterm.once("variablesview-fetched", openVariablesView);
+
+ let anchor = [...message.matched][0].querySelector("a");
+
+ executeSoon(() =>
+ EventUtils.synthesizeMouse(anchor, 2, 2, {}, hud.iframeWindow)
+ );
+
+ return deferred.promise;
+}
+
+function testJSTermIsVisible(hud) {
+ let inputContainer = hud.ui.window.document.querySelector(".jsterm-input-container");
+ isnot(inputContainer.style.display, "none", "input is visible");
+}
+
+function testObjectInspectorPropertiesAreSet(variablesView) {
+ isnot(variablesView.eval, null, "vview.eval is set");
+ isnot(variablesView.switch, null, "vview.switch is set");
+ isnot(variablesView.delete, null, "vview.delete is set");
+}
+
+function testJSTermIsNotVisible(hud) {
+ let inputContainer = hud.ui.window.document.querySelector(".jsterm-input-container");
+ is(inputContainer.style.display, "none", "input is not visible");
+}
+
+function* testRunner() {
+ let browserConsole, webConsole, variablesView;
+
+ Services.prefs.setBoolPref("devtools.chrome.enabled", true);
+
+ browserConsole = yield HUDService.toggleBrowserConsole();
+ variablesView = yield getVariablesView(browserConsole);
+ testJSTermIsVisible(browserConsole);
+ testObjectInspectorPropertiesAreSet(variablesView);
+
+ let {tab: browserTab} = yield loadTab("data:text/html;charset=utf8,hello world");
+ webConsole = yield openConsole(browserTab);
+ variablesView = yield getVariablesView(webConsole);
+ testJSTermIsVisible(webConsole)
+ testObjectInspectorPropertiesAreSet(variablesView)
+ yield closeConsole(browserTab);
+
+ yield HUDService.toggleBrowserConsole();
+ Services.prefs.setBoolPref("devtools.chrome.enabled", false);
+
+ browserConsole = yield HUDService.toggleBrowserConsole();
+ variablesView = yield getVariablesView(browserConsole);
+ testJSTermIsNotVisible(browserConsole);
+ testObjectInspectorPropertiesAreNotSet(variablesView);
+
+ webConsole = yield openConsole(browserTab);
+ variablesView = yield getVariablesView(webConsole);
+ testJSTermIsVisible(webConsole)
+ testObjectInspectorPropertiesAreSet(variablesView)
+ yield closeConsole(browserTab);
+}
+
+function test() {
+ Task.spawn(testRunner).then(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_console_iframe_messages.js b/browser/devtools/webconsole/test/browser_console_iframe_messages.js
new file mode 100644
index 000000000..f8e32b23a
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_iframe_messages.js
@@ -0,0 +1,104 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that cached messages from nested iframes are displayed in the
+// Web/Browser Console.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-consoleiframes.html";
+
+const expectedMessages = [
+ {
+ text: "main file",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "blah",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR
+ },
+ {
+ text: "iframe 2",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG
+ },
+ {
+ text: "iframe 3",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG
+ }
+];
+
+// "iframe 1" console messages can be coalesced into one if they follow each
+// other in the sequence of messages (depending on timing). If they do not, then
+// they will be displayed in the console output independently, as separate
+// messages. This is why we need to match any of the following two rules.
+const expectedMessagesAny = [
+ {
+ name: "iframe 1 (count: 2)",
+ text: "iframe 1",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ count: 2
+ },
+ {
+ name: "iframe 1 (repeats: 2)",
+ text: "iframe 1",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ repeats: 2
+ },
+];
+
+function test()
+{
+ expectUncaughtException();
+ loadTab(TEST_URI).then(() => {
+ openConsole().then(consoleOpened);
+ });
+}
+
+function consoleOpened(hud)
+{
+ ok(hud, "web console opened");
+
+ waitForMessages({
+ webconsole: hud,
+ messages: expectedMessages,
+ }).then(() => {
+ info("first messages matched");
+ waitForMessages({
+ webconsole: hud,
+ messages: expectedMessagesAny,
+ matchCondition: "any",
+ }).then(() => {
+ closeConsole().then(onWebConsoleClose);
+ });
+ });
+}
+
+function onWebConsoleClose()
+{
+ info("web console closed");
+ HUDService.toggleBrowserConsole().then(onBrowserConsoleOpen);
+}
+
+function onBrowserConsoleOpen(hud)
+{
+ ok(hud, "browser console opened");
+ waitForMessages({
+ webconsole: hud,
+ messages: expectedMessages,
+ }).then(() => {
+ info("first messages matched");
+ waitForMessages({
+ webconsole: hud,
+ messages: expectedMessagesAny,
+ matchCondition: "any",
+ }).then(() => {
+ closeConsole().then(finishTest);
+ });
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_console_keyboard_accessibility.js b/browser/devtools/webconsole/test/browser_console_keyboard_accessibility.js
new file mode 100644
index 000000000..bbf767314
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_keyboard_accessibility.js
@@ -0,0 +1,79 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that basic keyboard shortcuts work in the web console.
+
+"use strict";
+
+let test = asyncTest(function*() {
+ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+ ok(hud, "Web Console opened");
+
+ info("dump some spew into the console for scrolling");
+ hud.jsterm.execute("(function() { for (var i = 0; i < 100; i++) { " +
+ "console.log('foobarz' + i);" +
+ "}})();");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "foobarz99",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ let currentPosition = hud.outputNode.parentNode.scrollTop;
+ let bottom = currentPosition;
+
+ EventUtils.synthesizeKey("VK_PAGE_UP", {});
+ isnot(hud.outputNode.parentNode.scrollTop, currentPosition, "scroll position changed after page up");
+
+ currentPosition = hud.outputNode.parentNode.scrollTop;
+ EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
+ ok(hud.outputNode.parentNode.scrollTop > currentPosition, "scroll position now at bottom");
+
+ EventUtils.synthesizeKey("VK_HOME", {});
+ is(hud.outputNode.parentNode.scrollTop, 0, "scroll position now at top");
+
+ EventUtils.synthesizeKey("VK_END", {});
+
+ let scrollTop = hud.outputNode.parentNode.scrollTop;
+ ok(scrollTop > 0 && Math.abs(scrollTop - bottom) <= 5,
+ "scroll position now at bottom");
+
+ info("try ctrl-l to clear output");
+ executeSoon(() => { EventUtils.synthesizeKey("l", { ctrlKey: true }); });
+ yield hud.jsterm.once("messages-cleared");
+
+ is(hud.outputNode.textContent.indexOf("foobarz1"), -1, "output cleared");
+ is(hud.jsterm.inputNode.getAttribute("focused"), "true",
+ "jsterm input is focused");
+
+ info("try ctrl-f to focus filter");
+ EventUtils.synthesizeKey("F", { accelKey: true });
+ ok(!hud.jsterm.inputNode.getAttribute("focused"),
+ "jsterm input is not focused");
+ is(hud.ui.filterBox.getAttribute("focused"), "true",
+ "filter input is focused");
+
+ if (Services.appinfo.OS == "Darwin") {
+ ok(hud.ui.getFilterState("network"), "network category is enabled");
+ EventUtils.synthesizeKey("t", { ctrlKey: true });
+ ok(!hud.ui.getFilterState("network"), "accesskey for Network works");
+ EventUtils.synthesizeKey("t", { ctrlKey: true });
+ ok(hud.ui.getFilterState("network"), "accesskey for Network works (again)");
+ }
+ else {
+ EventUtils.synthesizeKey("N", { altKey: true });
+ let net = hud.ui.document.querySelector("toolbarbutton[category=net]");
+ is(hud.ui.document.activeElement, net,
+ "accesskey for Network category focuses the Net button");
+ }
+});
diff --git a/browser/devtools/webconsole/test/browser_console_log_inspectable_object.js b/browser/devtools/webconsole/test/browser_console_log_inspectable_object.js
new file mode 100644
index 000000000..075f2fb70
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_log_inspectable_object.js
@@ -0,0 +1,50 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that objects given to console.log() are inspectable.
+
+"use strict";
+
+let test = asyncTest(function*() {
+ yield loadTab("data:text/html;charset=utf8,test for bug 676722 - inspectable objects for window.console");
+
+ let hud = yield openConsole();
+ hud.jsterm.clearOutput(true);
+
+ hud.jsterm.execute("myObj = {abba: 'omgBug676722'}");
+ hud.jsterm.execute("console.log('fooBug676722', myObj)");
+
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "fooBug676722",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ objects: true,
+ }],
+ });
+
+ let msg = [...result.matched][0];
+ ok(msg, "message element");
+
+ let body = msg.querySelector(".message-body");
+ ok(body, "message body");
+
+ let clickable = result.clickableElements[0];
+ ok(clickable, "the console.log() object anchor was found");
+ ok(body.textContent.contains('{ abba: "omgBug676722" }'),
+ "clickable node content is correct");
+
+ executeSoon(() => {
+ EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
+ });
+
+ let varView = yield hud.jsterm.once("variablesview-fetched");
+ ok(varView, "object inspector opened on click");
+
+ yield findVariableViewProperties(varView, [{
+ name: "abba",
+ value: "omgBug676722",
+ }], { webconsole: hud });
+});
diff --git a/browser/devtools/webconsole/test/browser_console_native_getters.js b/browser/devtools/webconsole/test/browser_console_native_getters.js
new file mode 100644
index 000000000..8213108ab
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_native_getters.js
@@ -0,0 +1,99 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that native getters and setters for DOM elements work as expected in
+// variables view - bug 870220.
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf8,<title>bug870220</title>\n" +
+ "<p>hello world\n<p>native getters!";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ let jsterm = hud.jsterm;
+
+ jsterm.execute("document");
+
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "HTMLDocument \u2192 data:text/html;charset=utf8",
+ category: CATEGORY_OUTPUT,
+ objects: true,
+ }],
+ });
+
+ let clickable = result.clickableElements[0];
+ ok(clickable, "clickable object found");
+
+ executeSoon(() => {
+ EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
+ });
+
+ let fetchedVar = yield jsterm.once("variablesview-fetched");
+
+ let variablesView = fetchedVar._variablesView;
+ ok(variablesView, "variables view object");
+
+ let results = yield findVariableViewProperties(fetchedVar, [
+ { name: "title", value: "bug870220" },
+ { name: "bgColor" },
+ ], { webconsole: hud });
+
+ let prop = results[1].matchedProp;
+ ok(prop, "matched the |bgColor| property in the variables view");
+
+ // Check that property value updates work.
+ let updatedVar = yield updateVariablesViewProperty({
+ property: prop,
+ field: "value",
+ string: "'red'",
+ webconsole: hud,
+ });
+
+ info("on fetch after background update");
+
+ jsterm.clearOutput(true);
+ jsterm.execute("document.bgColor");
+
+ [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "red",
+ category: CATEGORY_OUTPUT,
+ }],
+ });
+
+ yield findVariableViewProperties(updatedVar, [
+ { name: "bgColor", value: "red" },
+ ], { webconsole: hud });
+
+ jsterm.execute("$$('p')");
+
+ [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "NodeList [",
+ category: CATEGORY_OUTPUT,
+ objects: true,
+ }],
+ });
+
+ clickable = result.clickableElements[0];
+ ok(clickable, "clickable object found");
+
+ executeSoon(() => {
+ EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
+ });
+
+ fetchedVar = yield jsterm.once("variablesview-fetched");
+
+ yield findVariableViewProperties(fetchedVar, [
+ { name: "0.textContent", value: /hello world/ },
+ { name: "1.textContent", value: /native getters/ },
+ ], { webconsole: hud });
+});
diff --git a/browser/devtools/webconsole/test/browser_console_navigation_marker.js b/browser/devtools/webconsole/test/browser_console_navigation_marker.js
new file mode 100644
index 000000000..280c525ae
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_navigation_marker.js
@@ -0,0 +1,75 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that the navigation marker shows on page reload - bug 793996.
+
+const PREF = "devtools.webconsole.persistlog";
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let hud;
+
+let test = asyncTest(function* () {
+ Services.prefs.setBoolPref(PREF, true);
+
+ let { browser } = yield loadTab(TEST_URI);
+ hud = yield openConsole();
+
+ yield consoleOpened();
+
+ let loaded = loadBrowser(browser);
+ BrowserReload();
+ yield loaded;
+
+ yield onReload();
+
+ isnot(hud.outputNode.textContent.indexOf("foobarz1"), -1,
+ "foobarz1 is still in the output");
+
+ Services.prefs.clearUserPref(PREF);
+
+ hud = null;
+});
+
+function consoleOpened()
+{
+ ok(hud, "Web Console opened");
+
+ hud.jsterm.clearOutput();
+ content.console.log("foobarz1");
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "foobarz1",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+}
+
+function onReload()
+{
+ content.console.log("foobarz2");
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "page reload",
+ text: "test-console.html",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "foobarz2",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ },
+ {
+ name: "navigation marker",
+ text: "test-console.html",
+ type: Messages.NavigationMarker,
+ }],
+ });
+}
+
diff --git a/browser/devtools/webconsole/test/browser_console_nsiconsolemessage.js b/browser/devtools/webconsole/test/browser_console_nsiconsolemessage.js
new file mode 100644
index 000000000..ca87c057f
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_nsiconsolemessage.js
@@ -0,0 +1,80 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that nsIConsoleMessages are displayed in the Browser Console.
+// See bug 859756.
+
+const TEST_URI = "data:text/html;charset=utf8,<title>bug859756</title>\n" +
+ "<p>hello world\n<p>nsIConsoleMessages ftw!";
+
+function test()
+{
+ const FILTER_PREF = "devtools.browserconsole.filter.jslog";
+ Services.prefs.setBoolPref(FILTER_PREF, true);
+
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref(FILTER_PREF);
+ });
+
+ Task.spawn(function*() {
+ const {tab} = yield loadTab(TEST_URI);
+
+ // Test for cached nsIConsoleMessages.
+ Services.console.logStringMessage("test1 for bug859756");
+
+ info("open web console");
+ let hud = yield openConsole(tab);
+
+ ok(hud, "web console opened");
+ Services.console.logStringMessage("do-not-show-me");
+ content.console.log("foobarz");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "foobarz",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ let text = hud.outputNode.textContent;
+ is(text.indexOf("do-not-show-me"), -1,
+ "nsIConsoleMessages are not displayed");
+ is(text.indexOf("test1 for bug859756"), -1,
+ "nsIConsoleMessages are not displayed (confirmed)");
+
+ yield closeConsole(tab);
+
+ info("web console closed");
+ hud = yield HUDService.toggleBrowserConsole();
+ ok(hud, "browser console opened");
+
+ Services.console.logStringMessage("test2 for bug859756");
+
+ let results = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "test1 for bug859756",
+ category: CATEGORY_JS,
+ }, {
+ text: "test2 for bug859756",
+ category: CATEGORY_JS,
+ }, {
+ text: "do-not-show-me",
+ category: CATEGORY_JS,
+ }],
+ });
+
+ let msg = [...results[2].matched][0];
+ ok(msg, "message element for do-not-show-me (nsIConsoleMessage)");
+ isnot(msg.textContent.indexOf("do-not-show"), -1, "element content is correct");
+ ok(!msg.classList.contains("filtered-by-type"), "element is not filtered");
+
+ hud.setFilterState("jslog", false);
+
+ ok(msg.classList.contains("filtered-by-type"), "element is filtered");
+ }).then(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_console_open_or_focus.js b/browser/devtools/webconsole/test/browser_console_open_or_focus.js
new file mode 100644
index 000000000..94625bc10
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_open_or_focus.js
@@ -0,0 +1,47 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that the "browser console" menu item opens or focuses (if already open)
+// the console window instead of toggling it open/close.
+
+
+"use strict";
+
+let test = asyncTest(function* () {
+ let wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ let currWindow, hud, mainWindow;
+
+ mainWindow = Services.wm.getMostRecentWindow(null);
+
+ yield HUDService.openBrowserConsoleOrFocus();
+
+ hud = HUDService.getBrowserConsole();
+
+ console.log("testmessage");
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "testmessage"
+ }],
+ });
+
+ currWindow = Services.wm.getMostRecentWindow(null);
+ is(currWindow.document.documentURI, devtools.Tools.webConsole.url,
+ "The Browser Console is open and has focus");
+
+ mainWindow.focus();
+
+ yield HUDService.openBrowserConsoleOrFocus();
+
+ currWindow = Services.wm.getMostRecentWindow(null);
+ is(currWindow.document.documentURI, devtools.Tools.webConsole.url,
+ "The Browser Console is open and has focus");
+
+ yield HUDService.toggleBrowserConsole();
+
+ hud = HUDService.getBrowserConsole();
+ ok(!hud, "Browser Console has been closed");
+});
diff --git a/browser/devtools/webconsole/test/browser_console_optimized_out_vars.js b/browser/devtools/webconsole/test/browser_console_optimized_out_vars.js
new file mode 100644
index 000000000..c3eceedf0
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_optimized_out_vars.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Check that inspecting an optimized out variable works when execution is
+// paused.
+
+function test() {
+ Task.spawn(function* () {
+ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-closure-optimized-out.html";
+ let {tab} = yield loadTab(TEST_URI);
+ let hud = yield openConsole(tab);
+ let { toolbox, panel, panelWin } = yield openDebugger();
+
+ yield waitForThreadEvents(panel, "resumed");
+ ok(true, "Debugger resumed");
+
+ let sources = panelWin.DebuggerView.Sources;
+ yield panel.addBreakpoint({ actor: sources.values[0], line: 18 });
+ yield ensureThreadClientState(panel, "resumed");
+
+ let fetchedScopes = panelWin.once(panelWin.EVENTS.FETCHED_SCOPES);
+ let button = content.document.querySelector("button");
+ ok(button, "Button element found");
+ // Spin the event loop before causing the debuggee to pause, to allow
+ // this function to return first.
+ executeSoon(() => button.click());
+
+ let packet = yield fetchedScopes;
+ ok(true, "Scopes were fetched");
+
+ yield toolbox.selectTool("webconsole");
+
+ // This is the meat of the test: evaluate the optimized out variable.
+ hud.jsterm.execute("upvar");
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "optimized out",
+ category: CATEGORY_OUTPUT,
+ }]
+ });
+
+ finishTest();
+ }).then(null, aError => {
+ ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+ });
+}
+
+// Debugger helper functions stolen from browser/devtools/debugger/test/head.js.
+
+function ensureThreadClientState(aPanel, aState) {
+ let thread = aPanel.panelWin.gThreadClient;
+ let state = thread.state;
+
+ info("Thread is: '" + state + "'.");
+
+ if (state == aState) {
+ return promise.resolve(null);
+ } else {
+ return waitForThreadEvents(aPanel, aState);
+ }
+}
+
+function waitForThreadEvents(aPanel, aEventName, aEventRepeat = 1) {
+ info("Waiting for thread event: '" + aEventName + "' to fire: " + aEventRepeat + " time(s).");
+
+ let deferred = promise.defer();
+ let thread = aPanel.panelWin.gThreadClient;
+ let count = 0;
+
+ thread.addListener(aEventName, function onEvent(aEventName, ...aArgs) {
+ info("Thread event '" + aEventName + "' fired: " + (++count) + " time(s).");
+
+ if (count == aEventRepeat) {
+ ok(true, "Enough '" + aEventName + "' thread events have been fired.");
+ thread.removeListener(aEventName, onEvent);
+ deferred.resolve.apply(deferred, aArgs);
+ }
+ });
+
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_console_private_browsing.js b/browser/devtools/webconsole/test/browser_console_private_browsing.js
new file mode 100644
index 000000000..f517fdd2c
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_private_browsing.js
@@ -0,0 +1,200 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Bug 874061: test for how the browser and web consoles display messages coming
+// from private windows. See bug for description of expected behavior.
+
+function test()
+{
+ const TEST_URI = "data:text/html;charset=utf8,<p>hello world! bug 874061" +
+ "<button onclick='console.log(\"foobar bug 874061\");" +
+ "fooBazBaz.yummy()'>click</button>";
+ let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
+ .getService(Ci.nsIConsoleAPIStorage);
+ let privateWindow, privateBrowser, privateTab, privateContent;
+ let hud, expectedMessages, nonPrivateMessage;
+
+ // This test is slightly more involved: it opens the web console twice,
+ // a new private window once, and the browser console twice. We can get
+ // a timeout with debug builds on slower machines.
+ requestLongerTimeout(2);
+ start();
+
+ function start()
+ {
+ gBrowser.selectedTab = gBrowser.addTab("data:text/html;charset=utf8," +
+ "<p>hello world! I am not private!");
+ gBrowser.selectedBrowser.addEventListener("load", onLoadTab, true);
+ }
+
+ function onLoadTab()
+ {
+ gBrowser.selectedBrowser.removeEventListener("load", onLoadTab, true);
+ info("onLoadTab()");
+
+ // Make sure we have a clean state to start with.
+ Services.console.reset();
+ ConsoleAPIStorage.clearEvents();
+
+ // Add a non-private message to the browser console.
+ content.console.log("bug874061-not-private");
+
+ nonPrivateMessage = {
+ name: "console message from a non-private window",
+ text: "bug874061-not-private",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ };
+
+ privateWindow = OpenBrowserWindow({ private: true });
+ ok(privateWindow, "new private window");
+ ok(PrivateBrowsingUtils.isWindowPrivate(privateWindow), "window is private");
+
+ whenDelayedStartupFinished(privateWindow, onPrivateWindowReady);
+ }
+
+ function onPrivateWindowReady()
+ {
+ info("private browser window opened");
+ privateBrowser = privateWindow.gBrowser;
+
+ privateTab = privateBrowser.selectedTab = privateBrowser.addTab(TEST_URI);
+ privateBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+ info("private tab opened");
+ privateBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+ privateContent = privateBrowser.selectedBrowser.contentWindow;
+ ok(PrivateBrowsingUtils.isBrowserPrivate(privateBrowser.selectedBrowser), "tab window is private");
+ openConsole(privateTab).then(consoleOpened);
+ }, true);
+ }
+
+ function addMessages()
+ {
+ let button = privateContent.document.querySelector("button");
+ ok(button, "button in page");
+ EventUtils.synthesizeMouse(button, 2, 2, {}, privateContent);
+ }
+
+ function consoleOpened(aHud)
+ {
+ hud = aHud;
+ ok(hud, "web console opened");
+
+ addMessages();
+ expectedMessages = [
+ {
+ name: "script error",
+ text: "fooBazBaz is not defined",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ },
+ {
+ name: "console message",
+ text: "foobar bug 874061",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ },
+ ];
+
+ // Make sure messages are displayed in the web console as they happen, even
+ // if this is a private tab.
+ waitForMessages({
+ webconsole: hud,
+ messages: expectedMessages,
+ }).then(testCachedMessages);
+ }
+
+ function testCachedMessages()
+ {
+ info("testCachedMessages()");
+ closeConsole(privateTab).then(() => {
+ info("web console closed");
+ openConsole(privateTab).then(consoleReopened);
+ });
+ }
+
+ function consoleReopened(aHud)
+ {
+ hud = aHud;
+ ok(hud, "web console reopened");
+
+ // Make sure that cached messages are displayed in the web console, even
+ // if this is a private tab.
+ waitForMessages({
+ webconsole: hud,
+ messages: expectedMessages,
+ }).then(testBrowserConsole);
+ }
+
+ function testBrowserConsole()
+ {
+ info("testBrowserConsole()");
+ closeConsole(privateTab).then(() => {
+ info("web console closed");
+ privateWindow.HUDService.toggleBrowserConsole().then(onBrowserConsoleOpen);
+ });
+ }
+
+ // Make sure that the cached messages from private tabs are not displayed in
+ // the browser console.
+ function checkNoPrivateMessages()
+ {
+ let text = hud.outputNode.textContent;
+ is(text.indexOf("fooBazBaz"), -1, "no exception displayed");
+ is(text.indexOf("bug 874061"), -1, "no console message displayed");
+ }
+
+ function onBrowserConsoleOpen(aHud)
+ {
+ hud = aHud;
+ ok(hud, "browser console opened");
+
+ checkNoPrivateMessages();
+ addMessages();
+ expectedMessages.push(nonPrivateMessage);
+
+ // Make sure that live messages are displayed in the browser console, even
+ // from private tabs.
+ waitForMessages({
+ webconsole: hud,
+ messages: expectedMessages,
+ }).then(testPrivateWindowClose);
+ }
+
+ function testPrivateWindowClose()
+ {
+ info("close the private window and check if the private messages are removed");
+ hud.jsterm.once("private-messages-cleared", () => {
+ isnot(hud.outputNode.textContent.indexOf("bug874061-not-private"), -1,
+ "non-private messages are still shown after private window closed");
+ checkNoPrivateMessages();
+
+ info("close the browser console");
+ privateWindow.HUDService.toggleBrowserConsole().then(() => {
+ info("reopen the browser console");
+ executeSoon(() =>
+ HUDService.toggleBrowserConsole().then(onBrowserConsoleReopen));
+ });
+ });
+ privateWindow.BrowserTryToCloseWindow();
+ }
+
+ function onBrowserConsoleReopen(aHud)
+ {
+ hud = aHud;
+ ok(hud, "browser console reopened");
+
+ // Make sure that the non-private message is still shown after reopen.
+ waitForMessages({
+ webconsole: hud,
+ messages: [nonPrivateMessage],
+ }).then(() => {
+ // Make sure that no private message is displayed after closing the private
+ // window and reopening the Browser Console.
+ checkNoPrivateMessages();
+ executeSoon(finishTest);
+ });
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_console_variables_view.js b/browser/devtools/webconsole/test/browser_console_variables_view.js
new file mode 100644
index 000000000..bfd5a128a
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_variables_view.js
@@ -0,0 +1,189 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that variables view works as expected in the web console.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-eval-in-stackframe.html";
+
+let gWebConsole, gJSTerm, gVariablesView;
+
+let hud;
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ hud = yield openConsole();
+
+ gWebConsole = hud;
+ gJSTerm = hud.jsterm;
+ let msg = yield gJSTerm.execute("fooObj");
+
+ ok(msg, "output message found");
+ ok(msg.textContent.contains('{ testProp: "testValue" }'), "message text check");
+
+ let anchor = msg.querySelector("a");
+ ok(anchor, "object link found");
+
+ let fetched = gJSTerm.once("variablesview-fetched");
+
+ // executeSoon
+ EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow);
+
+ let view = yield fetched;
+
+ let results = yield onFooObjFetch(view);
+
+ let vView = yield onTestPropFound(results);
+ let results2 = yield onFooObjFetchAfterUpdate(vView);
+
+ let vView2 = yield onUpdatedTestPropFound(results2);
+ let results3 = yield onFooObjFetchAfterPropRename(vView2);
+
+ let vView3 = yield onRenamedTestPropFound(results3);
+ let results4 = yield onPropUpdateError(vView3);
+
+ yield onRenamedTestPropFoundAgain(results4);
+
+ let prop = results4[0].matchedProp;
+ yield testPropDelete(prop);
+
+ gWebConsole = gJSTerm = gVariablesView = null;
+});
+
+function onFooObjFetch(aVar)
+{
+ gVariablesView = aVar._variablesView;
+ ok(gVariablesView, "variables view object");
+
+ return findVariableViewProperties(aVar, [
+ { name: "testProp", value: "testValue" },
+ ], { webconsole: gWebConsole });
+}
+
+function onTestPropFound(aResults)
+{
+ let prop = aResults[0].matchedProp;
+ ok(prop, "matched the |testProp| property in the variables view");
+
+ is("testValue", aResults[0].value,
+ "|fooObj.testProp| value is correct");
+
+ // Check that property value updates work and that jsterm functions can be
+ // used.
+ return updateVariablesViewProperty({
+ property: prop,
+ field: "value",
+ string: "document.title + window.location + $('p')",
+ webconsole: gWebConsole
+ });
+}
+
+function onFooObjFetchAfterUpdate(aVar)
+{
+ info("onFooObjFetchAfterUpdate");
+ let expectedValue = content.document.title + content.location
+ + '[object HTMLParagraphElement]';
+
+ return findVariableViewProperties(aVar, [
+ { name: "testProp", value: expectedValue },
+ ], { webconsole: gWebConsole });
+}
+
+function onUpdatedTestPropFound(aResults)
+{
+ let prop = aResults[0].matchedProp;
+ ok(prop, "matched the updated |testProp| property value");
+
+ is(content.wrappedJSObject.fooObj.testProp, aResults[0].value,
+ "|fooObj.testProp| value has been updated");
+
+ // Check that property name updates work.
+ return updateVariablesViewProperty({
+ property: prop,
+ field: "name",
+ string: "testUpdatedProp",
+ webconsole: gWebConsole
+ });
+}
+
+function onFooObjFetchAfterPropRename(aVar)
+{
+ info("onFooObjFetchAfterPropRename");
+
+ let para = content.wrappedJSObject.document.querySelector("p");
+ let expectedValue = content.document.title + content.location + para;
+
+ // Check that the new value is in the variables view.
+ return findVariableViewProperties(aVar, [
+ { name: "testUpdatedProp", value: expectedValue },
+ ], { webconsole: gWebConsole });
+}
+
+function onRenamedTestPropFound(aResults)
+{
+ let prop = aResults[0].matchedProp;
+ ok(prop, "matched the renamed |testProp| property");
+
+ ok(!content.wrappedJSObject.fooObj.testProp,
+ "|fooObj.testProp| has been deleted");
+ is(content.wrappedJSObject.fooObj.testUpdatedProp, aResults[0].value,
+ "|fooObj.testUpdatedProp| is correct");
+
+ // Check that property value updates that cause exceptions are reported in
+ // the web console output.
+ return updateVariablesViewProperty({
+ property: prop,
+ field: "value",
+ string: "foobarzFailure()",
+ webconsole: gWebConsole
+ });
+}
+
+function onPropUpdateError(aVar)
+{
+ info("onPropUpdateError");
+
+ let para = content.wrappedJSObject.document.querySelector("p");
+ let expectedValue = content.document.title + content.location + para;
+
+ // Make sure the property did not change.
+ return findVariableViewProperties(aVar, [
+ { name: "testUpdatedProp", value: expectedValue },
+ ], { webconsole: gWebConsole });
+}
+
+function onRenamedTestPropFoundAgain(aResults)
+{
+ let prop = aResults[0].matchedProp;
+ ok(prop, "matched the renamed |testProp| property again");
+
+ let outputNode = gWebConsole.outputNode;
+
+ return waitForMessages({
+ webconsole: gWebConsole,
+ messages: [{
+ name: "exception in property update reported in the web console output",
+ text: "foobarzFailure",
+ category: CATEGORY_OUTPUT,
+ severity: SEVERITY_ERROR,
+ }],
+ });
+}
+
+function testPropDelete(aProp)
+{
+ gVariablesView.window.focus();
+ aProp.focus();
+
+ executeSoon(() => {
+ EventUtils.synthesizeKey("VK_DELETE", {}, gVariablesView.window);
+ });
+
+ return waitForSuccess({
+ name: "property deleted",
+ timeout: 60000,
+ validator: () => !("testUpdatedProp" in content.wrappedJSObject.fooObj)
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_console_variables_view_dom_nodes.js b/browser/devtools/webconsole/test/browser_console_variables_view_dom_nodes.js
new file mode 100644
index 000000000..318392bdf
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_variables_view_dom_nodes.js
@@ -0,0 +1,56 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* 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";
+
+/* Test that ensures DOM nodes are rendered correctly in VariablesView. */
+
+function test() {
+ const TEST_URI = 'data:text/html;charset=utf-8, \
+ <html> \
+ <head> \
+ <title>Test for DOM nodes in variables view</title> \
+ </head> \
+ <body> \
+ <div></div> \
+ <div id="testID"></div> \
+ <div class="single-class"></div> \
+ <div class="multiple-classes another-class"></div> \
+ <div class="class-and-id" id="class-and-id"></div> \
+ <div class="multiple-classes-and-id another-class" \
+ id="multiple-classes-and-id"></div> \
+ <div class=" whitespace-start"></div> \
+ <div class="whitespace-end "></div> \
+ <div class="multiple spaces"></div> \
+ </body> \
+ </html>';
+
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ const {tab} = yield loadTab(TEST_URI);
+ const hud = yield openConsole(tab);
+ const jsterm = hud.jsterm;
+
+ let deferred = promise.defer();
+ jsterm.once("variablesview-fetched", (_, aVar) => deferred.resolve(aVar));
+ jsterm.execute("inspect(document.querySelectorAll('div'))");
+
+ let variableScope = yield deferred.promise;
+ ok(variableScope, "Variables view opened");
+
+ yield findVariableViewProperties(variableScope, [
+ { name: "0", value: "<div>"},
+ { name: "1", value: "<div#testID>"},
+ { name: "2", value: "<div.single-class>"},
+ { name: "3", value: "<div.multiple-classes.another-class>"},
+ { name: "4", value: "<div#class-and-id.class-and-id>"},
+ { name: "5", value: "<div#multiple-classes-and-id.multiple-classes-and-id.another-class>"},
+ { name: "6", value: "<div.whitespace-start>"},
+ { name: "7", value: "<div.whitespace-end>"},
+ { name: "8", value: "<div.multiple.spaces>"},
+ ], { webconsole: hud});
+
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_console_variables_view_dont_sort_non_sortable_classes_properties.js b/browser/devtools/webconsole/test/browser_console_variables_view_dont_sort_non_sortable_classes_properties.js
new file mode 100644
index 000000000..60d82f99e
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_variables_view_dont_sort_non_sortable_classes_properties.js
@@ -0,0 +1,101 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* 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";
+
+/* Test case that ensures Array and other list types are not sorted in variables
+ * view.
+ *
+ * The tested types are:
+ * - Array
+ * - Int8Array
+ * - Int16Array
+ * - Int32Array
+ * - Uint8Array
+ * - Uint16Array
+ * - Uint32Array
+ * - Uint8ClampedArray
+ * - Float32Array
+ * - Float64Array
+ * - NodeList
+ */
+
+function test() {
+ const TEST_URI = "data:text/html;charset=utf-8, \
+ <html> \
+ <head> \
+ <title>Test document for bug 977500</title> \
+ </head> \
+ <body> \
+ <div></div> \
+ <div></div> \
+ <div></div> \
+ <div></div> \
+ <div></div> \
+ <div></div> \
+ <div></div> \
+ <div></div> \
+ <div></div> \
+ <div></div> \
+ <div></div> \
+ <div></div> \
+ </body> \
+ </html>";
+
+ let jsterm;
+
+ function* runner() {
+ const typedArrayTypes = ["Int8Array", "Int16Array", "Int32Array",
+ "Uint8Array", "Uint16Array", "Uint32Array",
+ "Uint8ClampedArray", "Float32Array",
+ "Float64Array"];
+
+ const {tab} = yield loadTab(TEST_URI);
+ const hud = yield openConsole(tab);
+ jsterm = hud.jsterm;
+
+ // Create an ArrayBuffer of 80 bytes to test TypedArrays. 80 bytes is
+ // enough to get 10 items in all different TypedArrays.
+ yield jsterm.execute("let buf = new ArrayBuffer(80);");
+
+ // Array
+ yield testNotSorted("Array(0,1,2,3,4,5,6,7,8,9,10)");
+ // NodeList
+ yield testNotSorted("document.querySelectorAll('div')");
+
+ // Typed arrays.
+ for (let type of typedArrayTypes) {
+ yield testNotSorted("new " + type + "(buf)");
+ }
+ }
+
+ /**
+ * A helper that ensures the properties are not sorted when an object
+ * specified by aObject is inspected.
+ *
+ * @param string aObject
+ * A string that, once executed, creates and returns the object to
+ * inspect.
+ */
+ function testNotSorted(aObject) {
+ info("Testing " + aObject);
+ let deferred = promise.defer();
+ jsterm.once("variablesview-fetched", (_, aVar) => deferred.resolve(aVar));
+ jsterm.execute("inspect(" + aObject + ")");
+
+ let variableScope = yield deferred.promise;
+ ok(variableScope, "Variables view opened");
+
+ // If the properties are sorted: keys = ["0", "1", "10",...] <- incorrect
+ // If the properties are not sorted: keys = ["0", "1", "2",...] <- correct
+ let keyIterator = variableScope._store.keys();
+ is(keyIterator.next().value, "0", "First key is 0");
+ is(keyIterator.next().value, "1", "Second key is 1");
+
+ // If the properties are sorted, the next one will be 10.
+ is(keyIterator.next().value, "2", "Third key is 2, not 10");
+ }
+
+ Task.spawn(runner).then(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_console_variables_view_highlighter.js b/browser/devtools/webconsole/test/browser_console_variables_view_highlighter.js
new file mode 100644
index 000000000..6c7087a94
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_variables_view_highlighter.js
@@ -0,0 +1,97 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that variables view is linked to the inspector for highlighting and
+// selecting DOM nodes
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-952277-highlight-nodes-in-vview.html";
+
+let gWebConsole, gJSTerm, gVariablesView, gToolbox;
+
+function test()
+{
+ loadTab(TEST_URI).then(() => {
+ openConsole().then(hud => {
+ consoleOpened(hud);
+ })
+ });
+}
+
+function consoleOpened(hud)
+{
+ gWebConsole = hud;
+ gJSTerm = hud.jsterm;
+ gToolbox = gDevTools.getToolbox(hud.target);
+ gJSTerm.execute("document.querySelectorAll('p')").then(onQSAexecuted);
+}
+
+function onQSAexecuted(msg)
+{
+ ok(msg, "output message found");
+ let anchor = msg.querySelector("a");
+ ok(anchor, "object link found");
+
+ gJSTerm.once("variablesview-fetched", onNodeListVviewFetched);
+
+ executeSoon(() =>
+ EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow)
+ );
+}
+
+function onNodeListVviewFetched(aEvent, aVar)
+{
+ gVariablesView = aVar._variablesView;
+ ok(gVariablesView, "variables view object");
+
+ // Transform the vview into an array we can filter properties from
+ let props = [[id, prop] for([id, prop] of aVar)];
+ // These properties are the DOM nodes ones
+ props = props.filter(v => v[0].match(/[0-9]+/));
+
+ function hoverOverDomNodeVariableAndAssertHighlighter(index) {
+ if (props[index]) {
+ let prop = props[index][1];
+ let valueEl = prop._valueLabel;
+
+ gToolbox.once("node-highlight", () => {
+ ok(true, "The highlighter was shown on hover of the DOMNode");
+ gToolbox.highlighterUtils.unhighlight().then(() => {
+ clickOnDomNodeVariableAndAssertInspectorSelected(index);
+ });
+ });
+
+ // Rather than trying to emulate a mouseenter event, let's call the
+ // variable's highlightDomNode and see if it has the desired effect
+ prop.highlightDomNode();
+ } else {
+ finishUp();
+ }
+ }
+
+ function clickOnDomNodeVariableAndAssertInspectorSelected(index) {
+ let prop = props[index][1];
+
+ // Make sure the inspector is initialized so we can listen to its events
+ gToolbox.initInspector().then(() => {
+ // Rather than trying to click on the value here, let's just call the
+ // variable's openNodeInInspector function and see if it has the
+ // desired effect
+ prop.openNodeInInspector().then(() => {
+ is(gToolbox.currentToolId, "inspector", "The toolbox switched over the inspector on DOMNode click");
+ gToolbox.selectTool("webconsole").then(() => {
+ hoverOverDomNodeVariableAndAssertHighlighter(index + 1);
+ });
+ });
+ });
+ }
+
+ hoverOverDomNodeVariableAndAssertHighlighter(0);
+}
+
+function finishUp() {
+ gWebConsole = gJSTerm = gVariablesView = gToolbox = null;
+
+ finishTest();
+}
diff --git a/browser/devtools/webconsole/test/browser_console_variables_view_while_debugging.js b/browser/devtools/webconsole/test/browser_console_variables_view_while_debugging.js
new file mode 100644
index 000000000..0c6f56973
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_variables_view_while_debugging.js
@@ -0,0 +1,131 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that makes sure web console eval happens in the user-selected stackframe
+// from the js debugger, when changing the value of a property in the variables
+// view.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-eval-in-stackframe.html";
+
+let gWebConsole, gJSTerm, gDebuggerWin, gThread, gDebuggerController,
+ gStackframes, gVariablesView;
+
+function test()
+{
+ loadTab(TEST_URI).then(() => {
+ openConsole().then(consoleOpened);
+ });
+}
+
+function consoleOpened(hud)
+{
+ gWebConsole = hud;
+ gJSTerm = hud.jsterm;
+
+ executeSoon(() => {
+ info("openDebugger");
+ openDebugger().then(debuggerOpened);
+ });
+}
+
+function debuggerOpened(aResult)
+{
+ gDebuggerWin = aResult.panelWin;
+ gDebuggerController = gDebuggerWin.DebuggerController;
+ gThread = gDebuggerController.activeThread;
+ gStackframes = gDebuggerController.StackFrames;
+
+ executeSoon(() => {
+ gThread.addOneTimeListener("framesadded", onFramesAdded);
+
+ info("firstCall()");
+ content.wrappedJSObject.firstCall();
+ });
+}
+
+function onFramesAdded()
+{
+ info("onFramesAdded");
+
+ executeSoon(() =>
+ openConsole().then(() =>
+ gJSTerm.execute("fooObj").then(onExecuteFooObj)
+ )
+ );
+}
+
+
+function onExecuteFooObj(msg)
+{
+ ok(msg, "output message found");
+ ok(msg.textContent.contains('{ testProp2: "testValue2" }'), "message text check");
+
+ let anchor = msg.querySelector("a");
+ ok(anchor, "object link found");
+
+ gJSTerm.once("variablesview-fetched", onFooObjFetch);
+
+ executeSoon(() => EventUtils.synthesizeMouse(anchor, 2, 2, {},
+ gWebConsole.iframeWindow));
+}
+
+function onFooObjFetch(aEvent, aVar)
+{
+ gVariablesView = aVar._variablesView;
+ ok(gVariablesView, "variables view object");
+
+ findVariableViewProperties(aVar, [
+ { name: "testProp2", value: "testValue2" },
+ { name: "testProp", value: "testValue", dontMatch: true },
+ ], { webconsole: gWebConsole }).then(onTestPropFound);
+}
+
+function onTestPropFound(aResults)
+{
+ let prop = aResults[0].matchedProp;
+ ok(prop, "matched the |testProp2| property in the variables view");
+
+ // Check that property value updates work and that jsterm functions can be
+ // used.
+ updateVariablesViewProperty({
+ property: prop,
+ field: "value",
+ string: "document.title + foo2 + $('p')",
+ webconsole: gWebConsole
+ }).then(onFooObjFetchAfterUpdate);
+}
+
+function onFooObjFetchAfterUpdate(aVar)
+{
+ info("onFooObjFetchAfterUpdate");
+ let para = content.wrappedJSObject.document.querySelector("p");
+ let expectedValue = content.document.title + "foo2SecondCall" + para;
+
+ findVariableViewProperties(aVar, [
+ { name: "testProp2", value: expectedValue },
+ ], { webconsole: gWebConsole }).then(onUpdatedTestPropFound);
+}
+
+function onUpdatedTestPropFound(aResults)
+{
+ let prop = aResults[0].matchedProp;
+ ok(prop, "matched the updated |testProp2| property value");
+
+ // Check that testProp2 was updated.
+ executeSoon(() => gJSTerm.execute("fooObj.testProp2").then(onExecuteFooObjTestProp2));
+}
+
+function onExecuteFooObjTestProp2()
+{
+ let para = content.wrappedJSObject.document.querySelector("p");
+ let expected = content.document.title + "foo2SecondCall" + para;
+
+ isnot(gWebConsole.outputNode.textContent.indexOf(expected), -1,
+ "fooObj.testProp2 is correct");
+
+ gWebConsole = gJSTerm = gDebuggerWin = gThread = gDebuggerController =
+ gStackframes = gVariablesView = null;
+ executeSoon(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_console_variables_view_while_debugging_and_inspecting.js b/browser/devtools/webconsole/test/browser_console_variables_view_while_debugging_and_inspecting.js
new file mode 100644
index 000000000..55890c3f7
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_variables_view_while_debugging_and_inspecting.js
@@ -0,0 +1,129 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that makes sure web console eval works while the js debugger paused the
+// page, and while the inspector is active. See bug 886137.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-eval-in-stackframe.html";
+
+let gWebConsole, gJSTerm, gDebuggerWin, gThread, gDebuggerController,
+ gStackframes, gVariablesView;
+
+function test()
+{
+ loadTab(TEST_URI).then(() => {
+ openConsole().then(consoleOpened);
+ }, true);
+}
+
+function consoleOpened(hud)
+{
+ gWebConsole = hud;
+ gJSTerm = hud.jsterm;
+
+ info("openDebugger");
+ openDebugger().then(debuggerOpened);
+}
+
+function debuggerOpened(aResult)
+{
+ info("debugger opened");
+ gDebuggerWin = aResult.panelWin;
+ gDebuggerController = gDebuggerWin.DebuggerController;
+ gThread = gDebuggerController.activeThread;
+ gStackframes = gDebuggerController.StackFrames;
+
+ openInspector().then(inspectorOpened);
+}
+
+function inspectorOpened(aPanel)
+{
+ info("inspector opened");
+ gThread.addOneTimeListener("framesadded", onFramesAdded);
+
+ info("firstCall()");
+ content.wrappedJSObject.firstCall();
+}
+
+function onFramesAdded()
+{
+ info("onFramesAdded");
+
+ openConsole().then(() => gJSTerm.execute("fooObj").then(onExecuteFooObj));
+}
+
+function onExecuteFooObj(msg)
+{
+ ok(msg, "output message found");
+ ok(msg.textContent.contains('{ testProp2: "testValue2" }'),
+ "message text check");
+
+ let anchor = msg.querySelector("a");
+ ok(anchor, "object link found");
+
+ gJSTerm.once("variablesview-fetched", onFooObjFetch);
+
+ EventUtils.synthesizeMouse(anchor, 2, 2, {}, gWebConsole.iframeWindow);
+}
+
+function onFooObjFetch(aEvent, aVar)
+{
+ gVariablesView = aVar._variablesView;
+ ok(gVariablesView, "variables view object");
+
+ findVariableViewProperties(aVar, [
+ { name: "testProp2", value: "testValue2" },
+ { name: "testProp", value: "testValue", dontMatch: true },
+ ], { webconsole: gWebConsole }).then(onTestPropFound);
+}
+
+function onTestPropFound(aResults)
+{
+ let prop = aResults[0].matchedProp;
+ ok(prop, "matched the |testProp2| property in the variables view");
+
+ // Check that property value updates work and that jsterm functions can be
+ // used.
+ updateVariablesViewProperty({
+ property: prop,
+ field: "value",
+ string: "document.title + foo2 + $('p')",
+ webconsole: gWebConsole
+ }).then(onFooObjFetchAfterUpdate);
+}
+
+function onFooObjFetchAfterUpdate(aVar)
+{
+ info("onFooObjFetchAfterUpdate");
+ let para = content.wrappedJSObject.document.querySelector("p");
+ let expectedValue = content.document.title + "foo2SecondCall" + para;
+
+ findVariableViewProperties(aVar, [
+ { name: "testProp2", value: expectedValue },
+ ], { webconsole: gWebConsole }).then(onUpdatedTestPropFound);
+}
+
+function onUpdatedTestPropFound(aResults)
+{
+ let prop = aResults[0].matchedProp;
+ ok(prop, "matched the updated |testProp2| property value");
+
+ // Check that testProp2 was updated.
+ gJSTerm.execute("fooObj.testProp2").then(onExecuteFooObjTestProp2);
+}
+
+function onExecuteFooObjTestProp2()
+{
+ let para = content.wrappedJSObject.document.querySelector("p");
+ let expected = content.document.title + "foo2SecondCall" + para;
+
+ isnot(gWebConsole.outputNode.textContent.indexOf(expected), -1,
+ "fooObj.testProp2 is correct");
+
+ gWebConsole = gJSTerm = gDebuggerWin = gThread = gDebuggerController =
+ gStackframes = gVariablesView = null;
+
+ finishTest();
+}
diff --git a/browser/devtools/webconsole/test/browser_eval_in_debugger_stackframe.js b/browser/devtools/webconsole/test/browser_eval_in_debugger_stackframe.js
new file mode 100644
index 000000000..beed02552
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_eval_in_debugger_stackframe.js
@@ -0,0 +1,147 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that makes sure web console eval happens in the user-selected stackframe
+// from the js debugger.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-eval-in-stackframe.html";
+
+let gWebConsole, gJSTerm, gDebuggerWin, gThread, gDebuggerController, gStackframes;
+
+function test()
+{
+ loadTab(TEST_URI).then(() => {
+ openConsole().then(consoleOpened);
+ });
+}
+
+function consoleOpened(hud)
+{
+ gWebConsole = hud;
+ gJSTerm = hud.jsterm;
+ gJSTerm.execute("foo").then(onExecuteFoo);
+}
+
+function onExecuteFoo()
+{
+ isnot(gWebConsole.outputNode.textContent.indexOf("globalFooBug783499"), -1,
+ "|foo| value is correct");
+
+ gJSTerm.clearOutput();
+
+ // Test for Bug 690529 - Web Console and Scratchpad should evaluate
+ // expressions in the scope of the content window, not in a sandbox.
+ executeSoon(() => gJSTerm.execute("foo2 = 'newFoo'; window.foo2").then(onNewFoo2));
+}
+
+function onNewFoo2(msg)
+{
+ is(gWebConsole.outputNode.textContent.indexOf("undefined"), -1,
+ "|undefined| is not displayed after adding |foo2|");
+
+ ok(msg, "output result found");
+
+ isnot(msg.textContent.indexOf("newFoo"), -1,
+ "'newFoo' is displayed after adding |foo2|");
+
+ gJSTerm.clearOutput();
+
+ info("openDebugger");
+ executeSoon(() => openDebugger().then(debuggerOpened));
+}
+
+function debuggerOpened(aResult)
+{
+ gDebuggerWin = aResult.panelWin;
+ gDebuggerController = gDebuggerWin.DebuggerController;
+ gThread = gDebuggerController.activeThread;
+ gStackframes = gDebuggerController.StackFrames;
+
+ info("openConsole");
+ executeSoon(() =>
+ openConsole().then(() =>
+ gJSTerm.execute("foo + foo2").then(onExecuteFooAndFoo2)
+ )
+ );
+}
+
+function onExecuteFooAndFoo2()
+{
+ let expected = "globalFooBug783499newFoo";
+ isnot(gWebConsole.outputNode.textContent.indexOf(expected), -1,
+ "|foo + foo2| is displayed after starting the debugger");
+
+ executeSoon(() => {
+ gJSTerm.clearOutput();
+
+ info("openDebugger");
+ openDebugger().then(() => {
+ gThread.addOneTimeListener("framesadded", onFramesAdded);
+
+ info("firstCall()");
+ content.wrappedJSObject.firstCall();
+ });
+ });
+}
+
+function onFramesAdded()
+{
+ info("onFramesAdded, openConsole() now");
+ executeSoon(() =>
+ openConsole().then(() =>
+ gJSTerm.execute("foo + foo2").then(onExecuteFooAndFoo2InSecondCall)
+ )
+ );
+}
+
+function onExecuteFooAndFoo2InSecondCall()
+{
+ let expected = "globalFooBug783499foo2SecondCall";
+ isnot(gWebConsole.outputNode.textContent.indexOf(expected), -1,
+ "|foo + foo2| from |secondCall()|");
+
+ executeSoon(() => {
+ gJSTerm.clearOutput();
+
+ info("openDebugger and selectFrame(1)");
+
+ openDebugger().then(() => {
+ gStackframes.selectFrame(1);
+
+ info("openConsole");
+ executeSoon(() =>
+ openConsole().then(() =>
+ gJSTerm.execute("foo + foo2 + foo3").then(onExecuteFoo23InFirstCall)
+ )
+ );
+ });
+ });
+}
+
+function onExecuteFoo23InFirstCall()
+{
+ let expected = "fooFirstCallnewFoofoo3FirstCall";
+ isnot(gWebConsole.outputNode.textContent.indexOf(expected), -1,
+ "|foo + foo2 + foo3| from |firstCall()|");
+
+ executeSoon(() =>
+ gJSTerm.execute("foo = 'abba'; foo3 = 'bug783499'; foo + foo3").then(
+ onExecuteFooAndFoo3ChangesInFirstCall));
+}
+
+function onExecuteFooAndFoo3ChangesInFirstCall()
+{
+ let expected = "abbabug783499";
+ isnot(gWebConsole.outputNode.textContent.indexOf(expected), -1,
+ "|foo + foo3| updated in |firstCall()|");
+
+ is(content.wrappedJSObject.foo, "globalFooBug783499", "|foo| in content window");
+ is(content.wrappedJSObject.foo2, "newFoo", "|foo2| in content window");
+ ok(!content.wrappedJSObject.foo3, "|foo3| was not added to the content window");
+
+ gWebConsole = gJSTerm = gDebuggerWin = gThread = gDebuggerController =
+ gStackframes = null;
+ executeSoon(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_eval_in_debugger_stackframe2.js b/browser/devtools/webconsole/test/browser_eval_in_debugger_stackframe2.js
new file mode 100644
index 000000000..e94375181
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_eval_in_debugger_stackframe2.js
@@ -0,0 +1,63 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test to make sure that web console commands can fire while paused at a breakpoint
+// that was triggered from a JS call. Relies on asynchronous js evaluation over the
+// protocol - see Bug 1088861.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-eval-in-stackframe.html";
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+
+ info("open the web console");
+ let hud = yield openConsole();
+ let {jsterm} = hud;
+
+ info("open the debugger");
+ let {panel,panelWin} = yield openDebugger();
+ let {DebuggerController} = panelWin;
+ let {activeThread,StackFrames} = DebuggerController;
+
+ let firstCall = promise.defer();
+ let frameAdded = promise.defer();
+ executeSoon(() => {
+ info ("Executing firstCall");
+ activeThread.addOneTimeListener("framesadded", () => {
+ executeSoon(frameAdded.resolve);
+ });
+ jsterm.execute("firstCall()").then(firstCall.resolve);
+ });
+
+ info ("Waiting for a frame to be added");
+ yield frameAdded.promise;
+
+ info ("Executing basic command while paused");
+ yield executeAndConfirm(jsterm, "1 + 2", "3");
+
+ info ("Executing command using scoped variables while paused");
+ yield executeAndConfirm(jsterm, "foo + foo2", '"globalFooBug783499foo2SecondCall"');
+
+ info ("Resuming the thread");
+ activeThread.resume();
+
+ info ("Checking the first command (which is the last to resolve since it paused");
+ let node = yield firstCall.promise;
+ is (node.querySelector(".message-body").textContent,
+ "undefined",
+ "firstCall() returned correct value");
+});
+
+function* executeAndConfirm(jsterm, input, output) {
+ info ("Executing command `"+input+"`");
+
+ let node = yield jsterm.execute(input);
+
+ is (node.querySelector(".message-body").textContent,
+ output,
+ "Expected result from call to " + input);
+}
+
diff --git a/browser/devtools/webconsole/test/browser_jsterm_inspect.js b/browser/devtools/webconsole/test/browser_jsterm_inspect.js
new file mode 100644
index 000000000..6bc5f5559
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_jsterm_inspect.js
@@ -0,0 +1,28 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that the inspect() jsterm helper function works.
+
+const TEST_URI = "data:text/html;charset=utf8,<p>hello bug 869981";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+ let jsterm = hud.jsterm;
+
+ jsterm.execute("testProp = 'testValue'");
+
+ let fetched = jsterm.once("variablesview-fetched");
+ jsterm.execute("inspect(window)");
+ let variable = yield fetched;
+
+ ok(variable._variablesView, "variables view object");
+
+ yield findVariableViewProperties(variable, [
+ { name: "testProp", value: "testValue" },
+ { name: "document", value: /HTMLDocument \u2192 data:/ },
+ ], { webconsole: hud });
+});
diff --git a/browser/devtools/webconsole/test/browser_longstring_hang.js b/browser/devtools/webconsole/test/browser_longstring_hang.js
new file mode 100644
index 000000000..5d8dc04cc
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_longstring_hang.js
@@ -0,0 +1,53 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that very long strings do not hang the browser.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-859170-longstring-hang.html";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ info("wait for the initial long string");
+
+ let results = yield waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "find 'foobar', no 'foobaz', in long string output",
+ text: "foobar",
+ noText: "foobaz",
+ category: CATEGORY_WEBDEV,
+ longString: true,
+ },
+ ],
+ });
+
+ let clickable = results[0].longStrings[0];
+ ok(clickable, "long string ellipsis is shown");
+ clickable.scrollIntoView(false);
+
+ EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
+
+ info("wait for long string expansion");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "find 'foobaz' after expand, but no 'boom!' at the end",
+ text: "foobaz",
+ noText: "boom!",
+ category: CATEGORY_WEBDEV,
+ longString: false,
+ },
+ {
+ text: "too long to be displayed",
+ longString: false,
+ },
+ ],
+ });
+});
diff --git a/browser/devtools/webconsole/test/browser_netpanel_longstring_expand.js b/browser/devtools/webconsole/test/browser_netpanel_longstring_expand.js
new file mode 100644
index 000000000..d19c393a7
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_netpanel_longstring_expand.js
@@ -0,0 +1,307 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that the network panel works with LongStringActors.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test/test-image.png";
+
+const TEST_IMG_BASE64 =
+ "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAVRJREFU" +
+ "OI2lk7FLw0AUxr+YpC1CBqcMWfsvCCLdXFzqEJCgDl1EQRGxg9AhSBEJONhFhG52UCuFDjq5dxD8" +
+ "FwoO0qGDOBQkl7vLOeWa2EQDffDBvTu+373Hu1OEEJgntGgxGD6J+7fLXKbt5VNUyhsKAChRBQcP" +
+ "FVFeWskFGH694mZroCQqCLlAwPxcgJBP254CmAD5B7C7dgHLMLF3uzoL4DQEod+Z5sP1FizDxGgy" +
+ "BqfhLID9AahX29J89bwPFgMsSEAQglAf9WobhPpScbPXr4FQHyzIADTsDizDRMPuIOC+zEeTMZo9" +
+ "BwH3EfAMACccbtfGaDKGZZg423yUZrdrg3EqxQlPr0BTdTR7joREN2uqnlBmCwW1hIJagtev4f3z" +
+ "A16/JvfiigMSYyzqJXlw/XKUyOORMUaBor6YavgdjKa8xGOnidadmwtwsnMu18q83/kHSou+bFND" +
+ "Dr4AAAAASUVORK5CYII=";
+
+let testDriver;
+
+function test() {
+ loadTab(TEST_URI).then(() => {
+ openConsole().then(testNetworkPanel);
+ });
+}
+
+function testNetworkPanel() {
+ testDriver = testGen();
+ testDriver.next();
+}
+
+function checkIsVisible(aPanel, aList) {
+ for (let id in aList) {
+ let node = aPanel.document.getElementById(id);
+ let isVisible = aList[id];
+ is(node.style.display, (isVisible ? "block" : "none"), id + " isVisible=" + isVisible);
+ }
+}
+
+function checkNodeContent(aPanel, aId, aContent) {
+ let node = aPanel.document.getElementById(aId);
+ if (node == null) {
+ ok(false, "Tried to access node " + aId + " that doesn't exist!");
+ }
+ else if (node.textContent.indexOf(aContent) != -1) {
+ ok(true, "checking content of " + aId);
+ }
+ else {
+ ok(false, "Got false value for " + aId + ": " + node.textContent + " doesn't have " + aContent);
+ }
+}
+
+function checkNodeKeyValue(aPanel, aId, aKey, aValue) {
+ let node = aPanel.document.getElementById(aId);
+
+ let headers = node.querySelectorAll("th");
+ for (let i = 0; i < headers.length; i++) {
+ if (headers[i].textContent == (aKey + ":")) {
+ is(headers[i].nextElementSibling.textContent, aValue,
+ "checking content of " + aId + " for key " + aKey);
+ return;
+ }
+ }
+
+ ok(false, "content check failed for " + aId + ", key " + aKey);
+}
+
+function testGen() {
+ let hud = HUDService.getHudByWindow(content);
+ let filterBox = hud.ui.filterBox;
+
+ let headerValue = (new Array(456)).join("fooz bar");
+ let headerValueGrip = {
+ type: "longString",
+ initial: headerValue.substr(0, 123),
+ length: headerValue.length,
+ actor: "faktor",
+ _fullString: headerValue,
+ };
+
+ let imageContentGrip = {
+ type: "longString",
+ initial: TEST_IMG_BASE64.substr(0, 143),
+ length: TEST_IMG_BASE64.length,
+ actor: "faktor2",
+ _fullString: TEST_IMG_BASE64,
+ };
+
+ let postDataValue = (new Array(123)).join("post me");
+ let postDataGrip = {
+ type: "longString",
+ initial: postDataValue.substr(0, 172),
+ length: postDataValue.length,
+ actor: "faktor3",
+ _fullString: postDataValue,
+ };
+
+ let httpActivity = {
+ updates: ["responseContent", "eventTimings"],
+ discardRequestBody: false,
+ discardResponseBody: false,
+ startedDateTime: (new Date()).toISOString(),
+ request: {
+ url: TEST_IMG,
+ method: "GET",
+ cookies: [],
+ headers: [
+ { name: "foo", value: "bar" },
+ { name: "loongstring", value: headerValueGrip },
+ ],
+ postData: { text: postDataGrip },
+ },
+ response: {
+ httpVersion: "HTTP/3.14",
+ status: 2012,
+ statusText: "ddahl likes tacos :)",
+ headers: [
+ { name: "Content-Type", value: "image/png" },
+ ],
+ content: { mimeType: "image/png", text: imageContentGrip },
+ cookies: [],
+ },
+ timings: { wait: 15, receive: 23 },
+ };
+
+ let networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+
+ is(filterBox._netPanel, networkPanel,
+ "Network panel stored on the anchor object");
+
+ networkPanel._onUpdate = function() {
+ networkPanel._onUpdate = null;
+ executeSoon(function() {
+ testDriver.next();
+ });
+ };
+
+ yield undefined;
+
+ info("test 1: check if a header value is expandable");
+
+ checkIsVisible(networkPanel, {
+ requestCookie: false,
+ requestFormData: false,
+ requestBody: false,
+ requestBodyFetchLink: true,
+ responseContainer: true,
+ responseBody: false,
+ responseNoBody: false,
+ responseImage: true,
+ responseImageCached: false,
+ responseBodyFetchLink: true,
+ });
+
+ checkNodeKeyValue(networkPanel, "requestHeadersContent", "foo", "bar");
+ checkNodeKeyValue(networkPanel, "requestHeadersContent", "loongstring",
+ headerValueGrip.initial + "[\u2026]");
+
+ let webConsoleClient = networkPanel.webconsole.webConsoleClient;
+ let longStringFn = webConsoleClient.longString;
+
+ let expectedGrip = headerValueGrip;
+
+ function longStringClientProvider(aLongString)
+ {
+ is(aLongString, expectedGrip,
+ "longString grip is correct");
+
+ return {
+ initial: expectedGrip.initial,
+ length: expectedGrip.length,
+ substring: function(aStart, aEnd, aCallback) {
+ is(aStart, expectedGrip.initial.length,
+ "substring start is correct");
+ is(aEnd, expectedGrip.length,
+ "substring end is correct");
+
+ executeSoon(function() {
+ aCallback({
+ substring: expectedGrip._fullString.substring(aStart, aEnd),
+ });
+
+ executeSoon(function() {
+ testDriver.next();
+ });
+ });
+ },
+ };
+ }
+
+ webConsoleClient.longString = longStringClientProvider;
+
+ let clickable = networkPanel.document
+ .querySelector("#requestHeadersContent .longStringEllipsis");
+ ok(clickable, "long string ellipsis is shown");
+
+ EventUtils.sendMouseEvent({ type: "mousedown"}, clickable,
+ networkPanel.document.defaultView);
+
+ yield undefined;
+
+ clickable = networkPanel.document
+ .querySelector("#requestHeadersContent .longStringEllipsis");
+ ok(!clickable, "long string ellipsis is not shown");
+
+ checkNodeKeyValue(networkPanel, "requestHeadersContent", "loongstring",
+ expectedGrip._fullString);
+
+ info("test 2: check that response body image fetching works");
+ expectedGrip = imageContentGrip;
+
+ let imgNode = networkPanel.document.getElementById("responseImageNode");
+ ok(!imgNode.getAttribute("src"), "no image is displayed");
+
+ clickable = networkPanel.document.querySelector("#responseBodyFetchLink");
+ EventUtils.sendMouseEvent({ type: "mousedown"}, clickable,
+ networkPanel.document.defaultView);
+
+ yield undefined;
+
+ imgNode = networkPanel.document.getElementById("responseImageNode");
+ is(imgNode.getAttribute("src"), "data:image/png;base64," + TEST_IMG_BASE64,
+ "displayed image is correct");
+ is(clickable.style.display, "none", "#responseBodyFetchLink is not visible");
+
+ info("test 3: expand the request body");
+
+ expectedGrip = postDataGrip;
+
+ clickable = networkPanel.document.querySelector("#requestBodyFetchLink");
+ EventUtils.sendMouseEvent({ type: "mousedown"}, clickable,
+ networkPanel.document.defaultView);
+ yield undefined;
+
+ is(clickable.style.display, "none", "#requestBodyFetchLink is not visible");
+
+ checkIsVisible(networkPanel, {
+ requestBody: true,
+ requestBodyFetchLink: false,
+ });
+
+ checkNodeContent(networkPanel, "requestBodyContent", expectedGrip._fullString);
+
+ webConsoleClient.longString = longStringFn;
+
+ networkPanel.panel.hidePopup();
+
+ info("test 4: reponse body long text");
+
+ httpActivity.response.content.mimeType = "text/plain";
+ httpActivity.response.headers[0].value = "text/plain";
+
+ expectedGrip = imageContentGrip;
+
+ // Reset response.content.text to avoid caching of the full string.
+ httpActivity.response.content.text = expectedGrip;
+
+ networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+ is(filterBox._netPanel, networkPanel,
+ "Network panel stored on httpActivity object");
+
+ networkPanel._onUpdate = function() {
+ networkPanel._onUpdate = null;
+ executeSoon(function() {
+ testDriver.next();
+ });
+ };
+
+ yield undefined;
+
+ checkIsVisible(networkPanel, {
+ requestCookie: false,
+ requestFormData: false,
+ requestBody: true,
+ requestBodyFetchLink: false,
+ responseContainer: true,
+ responseBody: true,
+ responseNoBody: false,
+ responseImage: false,
+ responseImageCached: false,
+ responseBodyFetchLink: true,
+ });
+
+ checkNodeContent(networkPanel, "responseBodyContent", expectedGrip.initial);
+
+ webConsoleClient.longString = longStringClientProvider;
+
+ clickable = networkPanel.document.querySelector("#responseBodyFetchLink");
+ EventUtils.sendMouseEvent({ type: "mousedown"}, clickable,
+ networkPanel.document.defaultView);
+
+ yield undefined;
+
+ webConsoleClient.longString = longStringFn;
+ is(clickable.style.display, "none", "#responseBodyFetchLink is not visible");
+ checkNodeContent(networkPanel, "responseBodyContent", expectedGrip._fullString);
+
+ networkPanel.panel.hidePopup();
+
+ // All done!
+ testDriver = null;
+ executeSoon(finishTest);
+
+ yield undefined;
+}
diff --git a/browser/devtools/webconsole/test/browser_output_breaks_after_console_dir_uninspectable.js b/browser/devtools/webconsole/test/browser_output_breaks_after_console_dir_uninspectable.js
new file mode 100644
index 000000000..b7e86b94c
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_output_breaks_after_console_dir_uninspectable.js
@@ -0,0 +1,44 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Make sure that the Web Console output does not break after we try to call
+// console.dir() for objects that are not inspectable.
+
+const TEST_URI = "data:text/html;charset=utf8,test for bug 773466";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ hud.jsterm.clearOutput(true);
+
+ hud.jsterm.execute("console.log('fooBug773466a')");
+ hud.jsterm.execute("myObj = Object.create(null)");
+ hud.jsterm.execute("console.dir(myObj)");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "fooBug773466a",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ },
+ {
+ name: "console.dir output",
+ consoleDir: "[object Object]",
+ }],
+ })
+
+ content.console.log("fooBug773466b");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "fooBug773466b",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+});
diff --git a/browser/devtools/webconsole/test/browser_output_longstring_expand.js b/browser/devtools/webconsole/test/browser_output_longstring_expand.js
new file mode 100644
index 000000000..7d5d785c3
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_output_longstring_expand.js
@@ -0,0 +1,83 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that long strings can be expanded in the console output.
+
+const TEST_URI = "data:text/html;charset=utf8,test for bug 787981 - check that long strings can be expanded in the output.";
+
+let test = asyncTest(function* () {
+ let tempScope = {};
+ Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
+ let DebuggerServer = tempScope.DebuggerServer;
+
+ let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 4)).join("a") +
+ "foobar";
+ let initialString =
+ longString.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
+
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ hud.jsterm.clearOutput(true);
+ hud.jsterm.execute("console.log('bazbaz', '" + longString +"', 'boom')");
+
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "console.log output",
+ text: ["bazbaz", "boom", initialString],
+ noText: "foobar",
+ longString: true,
+ }],
+ });
+
+ let clickable = result.longStrings[0];
+ ok(clickable, "long string ellipsis is shown");
+
+ clickable.scrollIntoView(false);
+
+ EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "full string",
+ text: ["bazbaz", "boom", longString],
+ category: CATEGORY_WEBDEV,
+ longString: false,
+ }],
+ });
+
+ hud.jsterm.clearOutput(true);
+ let msg = yield execute(hud, "'" + longString +"'");
+
+ isnot(msg.textContent.indexOf(initialString), -1,
+ "initial string is shown");
+ is(msg.textContent.indexOf(longString), -1,
+ "full string is not shown");
+
+ clickable = msg.querySelector(".longStringEllipsis");
+ ok(clickable, "long string ellipsis is shown");
+
+ clickable.scrollIntoView(false);
+
+ EventUtils.synthesizeMouse(clickable, 3, 4, {}, hud.iframeWindow);
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "full string",
+ text: longString,
+ category: CATEGORY_OUTPUT,
+ longString: false,
+ }],
+ })
+});
+
+function execute(hud, str) {
+ let deferred = promise.defer();
+ hud.jsterm.execute(str, deferred.resolve);
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_repeated_messages_accuracy.js b/browser/devtools/webconsole/test/browser_repeated_messages_accuracy.js
new file mode 100644
index 000000000..bd092bbc2
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_repeated_messages_accuracy.js
@@ -0,0 +1,125 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that makes sure messages are not considered repeated when coming from
+// different lines of code, or from different severities, etc.
+// See bugs 720180 and 800510.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-repeated-messages.html";
+const PREF = "devtools.webconsole.persistlog";
+
+let test = asyncTest(function* () {
+ Services.prefs.setBoolPref(PREF, true);
+
+ let { browser } = yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ yield consoleOpened(hud);
+
+ let loaded = loadBrowser(browser);
+ BrowserReload();
+ yield loaded;
+
+ yield testCSSRepeats(hud);
+ yield testCSSRepeatsAfterReload(hud);
+ yield testConsoleRepeats(hud);
+
+ Services.prefs.clearUserPref(PREF);
+});
+
+function consoleOpened(hud) {
+ // Check that css warnings are not coalesced if they come from different lines.
+ info("waiting for 2 css warnings");
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "two css warnings",
+ category: CATEGORY_CSS,
+ count: 2,
+ repeats: 1,
+ }],
+ });
+}
+
+function testCSSRepeats(hud) {
+ info("wait for repeats after page reload");
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "two css warnings, repeated twice",
+ category: CATEGORY_CSS,
+ repeats: 2,
+ count: 2,
+ }],
+ });
+}
+
+function testCSSRepeatsAfterReload(hud) {
+ hud.jsterm.clearOutput(true);
+ hud.jsterm.execute("testConsole()");
+
+ info("wait for repeats with the console API");
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "console.log 'foo repeat' repeated twice",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ repeats: 2,
+ },
+ {
+ name: "console.log 'foo repeat' repeated once",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ repeats: 1,
+ },
+ {
+ name: "console.error 'foo repeat' repeated once",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_ERROR,
+ repeats: 1,
+ },
+ ],
+ })
+}
+
+function testConsoleRepeats(hud) {
+ hud.jsterm.clearOutput(true);
+ hud.jsterm.execute("undefined");
+
+ content.console.log("undefined");
+
+ info("make sure console API messages are not coalesced with jsterm output");
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "'undefined' jsterm input message",
+ text: "undefined",
+ category: CATEGORY_INPUT,
+ },
+ {
+ name: "'undefined' jsterm output message",
+ text: "undefined",
+ category: CATEGORY_OUTPUT,
+ },
+ {
+ name: "'undefined' console.log message",
+ text: "undefined",
+ category: CATEGORY_WEBDEV,
+ repeats: 1,
+ },
+ ],
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_result_format_as_string.js b/browser/devtools/webconsole/test/browser_result_format_as_string.js
new file mode 100644
index 000000000..70c3cc61a
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_result_format_as_string.js
@@ -0,0 +1,43 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Connection closed");
+
+// Make sure that JS eval result are properly formatted as strings.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-result-format-as-string.html";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ hud.jsterm.clearOutput(true);
+
+ let msg = yield execute(hud, "document.querySelector('p')");
+
+ is(hud.outputNode.textContent.indexOf("bug772506_content"), -1,
+ "no content element found");
+ ok(!hud.outputNode.querySelector("#foobar"), "no #foobar element found");
+
+ ok(msg, "eval output node found");
+ is(msg.textContent.indexOf("<div>"), -1,
+ "<div> string is not displayed");
+ isnot(msg.textContent.indexOf("<p>"), -1,
+ "<p> string is displayed");
+
+ EventUtils.synthesizeMouseAtCenter(msg, {type: "mousemove"});
+ ok(!gBrowser._bug772506, "no content variable");
+});
+
+function execute(hud, str) {
+ let deferred = promise.defer();
+ hud.jsterm.execute(str, deferred.resolve);
+ return deferred.promise;
+} \ No newline at end of file
diff --git a/browser/devtools/webconsole/test/browser_warn_user_about_replaced_api.js b/browser/devtools/webconsole/test/browser_warn_user_about_replaced_api.js
new file mode 100644
index 000000000..adef430ad
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_warn_user_about_replaced_api.js
@@ -0,0 +1,81 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const TEST_REPLACED_API_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-replaced-api.html";
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/testscript.js";
+const PREF = "devtools.webconsole.persistlog";
+
+let test = asyncTest(function* () {
+ Services.prefs.setBoolPref(PREF, true);
+
+ let { browser } = yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+
+ yield testWarningNotPresent(hud);
+
+ let loaded = loadBrowser(browser);
+ content.location = TEST_REPLACED_API_URI;
+ yield loaded;
+
+ let hud2 = yield openConsole();
+
+ yield testWarningPresent(hud2);
+
+ Services.prefs.clearUserPref(PREF);
+});
+
+function testWarningNotPresent(hud)
+{
+ let deferred = promise.defer();
+
+ is(hud.outputNode.textContent.indexOf("logging API"), -1,
+ "no warning displayed");
+
+ // Bug 862024: make sure the warning doesn't show after page reload.
+ info("reload " + TEST_URI);
+ executeSoon(() => content.location.reload());
+
+ waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "testscript.js",
+ category: CATEGORY_NETWORK,
+ }],
+ }).then(() => executeSoon(() => {
+ is(hud.outputNode.textContent.indexOf("logging API"), -1,
+ "no warning displayed");
+ closeConsole().then(deferred.resolve);
+ }));
+
+ return deferred.promise;
+}
+
+function testWarningPresent(hud)
+{
+ info("wait for the warning to show");
+ let deferred = promise.defer();
+
+ let warning = {
+ webconsole: hud,
+ messages: [{
+ text: /logging API .+ disabled by a script/,
+ category: CATEGORY_JS,
+ severity: SEVERITY_WARNING,
+ }],
+ };
+
+ waitForMessages(warning).then(() => {
+ hud.jsterm.clearOutput();
+
+ executeSoon(() => {
+ info("reload the test page and wait for the warning to show");
+ waitForMessages(warning).then(deferred.resolve);
+ content.location.reload();
+ });
+ });
+
+ return deferred.promise;
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_abbreviate_source_url.js b/browser/devtools/webconsole/test/browser_webconsole_abbreviate_source_url.js
new file mode 100644
index 000000000..1bc94b5d3
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_abbreviate_source_url.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that source URLs are abbreviated properly for display on the right-
+// hand side of the Web Console.
+
+function test() {
+ testAbbreviation("http://example.com/x.js", "x.js");
+ testAbbreviation("http://example.com/foo/bar/baz/boo.js", "boo.js");
+ testAbbreviation("http://example.com/foo/bar/", "bar");
+ testAbbreviation("http://example.com/foo.js?bar=1&baz=2", "foo.js");
+ testAbbreviation("http://example.com/foo/?bar=1&baz=2", "foo");
+
+ finishTest();
+}
+
+function testAbbreviation(aFullURL, aAbbreviatedURL) {
+ is(WebConsoleUtils.abbreviateSourceURL(aFullURL), aAbbreviatedURL, aFullURL +
+ " is abbreviated to " + aAbbreviatedURL);
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_allow_mixedcontent_securityerrors.js b/browser/devtools/webconsole/test/browser_webconsole_allow_mixedcontent_securityerrors.js
new file mode 100644
index 000000000..4913f5ccf
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_allow_mixedcontent_securityerrors.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// The test loads a web page with mixed active and display content
+// on it while the "block mixed content" settings are _off_.
+// It then checks that the loading mixed content warning messages
+// are logged to the console and have the correct "Learn More"
+// url appended to them.
+// Bug 875456 - Log mixed content messages from the Mixed Content
+// Blocker to the Security Pane in the Web Console
+
+const TEST_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-mixedcontent-securityerrors.html";
+const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/MixedContent";
+
+let test = asyncTest(function* () {
+ yield pushPrefEnv();
+
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ let results = yield waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "Logged mixed active content",
+ text: "Loading mixed (insecure) active content \"http://example.com/\" on a secure page",
+ category: CATEGORY_SECURITY,
+ severity: SEVERITY_WARNING,
+ objects: true,
+ },
+ {
+ name: "Logged mixed passive content - image",
+ text: "Loading mixed (insecure) display content \"http://example.com/tests/image/test/mochitest/blue.png\" on a secure page",
+ category: CATEGORY_SECURITY,
+ severity: SEVERITY_WARNING,
+ objects: true,
+ },
+ ],
+ });
+
+ yield testClickOpenNewTab(hud, results);
+});
+
+function pushPrefEnv()
+{
+ let deferred = promise.defer();
+ let options = {"set":
+ [["security.mixed_content.block_active_content", false],
+ ["security.mixed_content.block_display_content", false]
+ ]};
+ SpecialPowers.pushPrefEnv(options, deferred.resolve);
+ return deferred.promise;
+}
+
+function testClickOpenNewTab(hud, results) {
+ let warningNode = results[0].clickableElements[0];
+ ok(warningNode, "link element");
+ ok(warningNode.classList.contains("learn-more-link"), "link class name");
+ return simulateMessageLinkClick(warningNode, LEARN_MORE_URI);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_assert.js b/browser/devtools/webconsole/test/browser_webconsole_assert.js
new file mode 100644
index 000000000..10500a7a8
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_assert.js
@@ -0,0 +1,51 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that console.assert() works as expected (i.e. outputs only on falsy
+// asserts). See bug 760193.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-assert.html";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+ yield consoleOpened(hud);
+});
+
+function consoleOpened(hud) {
+ hud.jsterm.execute("test()");
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "undefined",
+ category: CATEGORY_OUTPUT,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "start",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "false assert",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_ERROR,
+ },
+ {
+ text: "falsy assert",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_ERROR,
+ },
+ {
+ text: "end",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ }).then(() => {
+ let nodes = hud.outputNode.querySelectorAll(".message");
+ is(nodes.length, 6, "only six messages are displayed, no output from the true assert");
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js b/browser/devtools/webconsole/test/browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js
new file mode 100644
index 000000000..0577b8cc2
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js
@@ -0,0 +1,40 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+// Test that properties starting with underscores or dollars can be
+// autocompleted (bug 967468).
+
+
+let test = asyncTest(function*() {
+ const TEST_URI = "data:text/html;charset=utf8,test autocompletion with $ or _";
+ yield loadTab(TEST_URI);
+
+ function autocomplete(term) {
+ let deferred = promise.defer();
+
+ jsterm.setInputValue(term);
+ jsterm.complete(jsterm.COMPLETE_HINT_ONLY, deferred.resolve);
+
+ yield deferred.promise;
+
+ ok(popup.itemCount > 0, "There's suggestions for '" + term + "'");
+ }
+
+ let { jsterm } = yield openConsole();
+ let popup = jsterm.autocompletePopup;
+
+ yield jsterm.execute("let testObject = {$$aaab: '', $$aaac: ''}");
+
+ // Should work with bug 967468.
+ yield autocomplete("Object.__d");
+ yield autocomplete("testObject.$$a");
+
+ // Here's when things go wrong in bug 967468.
+ yield autocomplete("Object.__de");
+ yield autocomplete("testObject.$$aa");
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_autocomplete_and_selfxss.js b/browser/devtools/webconsole/test/browser_webconsole_autocomplete_and_selfxss.js
new file mode 100644
index 000000000..69ccb443e
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_autocomplete_and_selfxss.js
@@ -0,0 +1,127 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>test for bug 642615";
+
+XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
+ "@mozilla.org/widget/clipboardhelper;1",
+ "nsIClipboardHelper");
+let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ yield consoleOpened(hud);
+});
+
+function consoleOpened(HUD) {
+ let deferred = promise.defer();
+
+ let jsterm = HUD.jsterm;
+ let stringToCopy = "foobazbarBug642615";
+
+ jsterm.clearOutput();
+
+ ok(!jsterm.completeNode.value, "no completeNode.value");
+
+ jsterm.setInputValue("doc");
+
+ let completionValue;
+
+ // wait for key "u"
+ function onCompletionValue() {
+ completionValue = jsterm.completeNode.value;
+
+ // Arguments: expected, setup, success, failure.
+ waitForClipboard(
+ stringToCopy,
+ function() {
+ clipboardHelper.copyString(stringToCopy, document);
+ },
+ onClipboardCopy,
+ finishTest);
+ }
+
+ function onClipboardCopy() {
+ testSelfXss();
+
+ jsterm.setInputValue("docu");
+ info("wait for completion update after clipboard paste");
+ updateEditUIVisibility();
+ jsterm.once("autocomplete-updated", onClipboardPaste);
+ goDoCommand("cmd_paste");
+ }
+
+
+ // Self xss prevention tests (bug 994134)
+ function testSelfXss(){
+ info("Self-xss paste tests")
+ WebConsoleUtils.usageCount = 0;
+ is(WebConsoleUtils.usageCount, 0, "Test for usage count getter")
+ // Input some commands to check if usage counting is working
+ for(let i = 0; i <= 3; i++){
+ jsterm.setInputValue(i);
+ jsterm.execute();
+ }
+ is(WebConsoleUtils.usageCount, 4, "Usage count incremented")
+ WebConsoleUtils.usageCount = 0;
+ updateEditUIVisibility();
+
+ let oldVal = jsterm.inputNode.value;
+ goDoCommand("cmd_paste");
+ let notificationbox = jsterm.hud.document.getElementById("webconsole-notificationbox");
+ let notification = notificationbox.getNotificationWithValue('selfxss-notification');
+ ok(notification, "Self-xss notification shown");
+ is(oldVal, jsterm.inputNode.value, "Paste blocked by self-xss prevention");
+
+ // Allow pasting
+ jsterm.inputNode.value = "allow pasting";
+ var evt = document.createEvent("KeyboardEvent");
+ evt.initKeyEvent ("keyup", true, true, window,
+ 0, 0, 0, 0,
+ 0, " ".charCodeAt(0));
+ jsterm.inputNode.dispatchEvent(evt);
+ jsterm.inputNode.value = "";
+ goDoCommand("cmd_paste");
+ isnot("", jsterm.inputNode.value, "Paste works");
+ }
+ function onClipboardPaste() {
+ ok(!jsterm.completeNode.value, "no completion value after paste");
+
+ info("wait for completion update after undo");
+ jsterm.once("autocomplete-updated", onCompletionValueAfterUndo);
+
+ // Get out of the webconsole event loop.
+ executeSoon(() => {
+ goDoCommand("cmd_undo");
+ });
+ }
+
+ function onCompletionValueAfterUndo() {
+ is(jsterm.completeNode.value, completionValue,
+ "same completeNode.value after undo");
+
+ info("wait for completion update after clipboard paste (ctrl-v)");
+ jsterm.once("autocomplete-updated", () => {
+ ok(!jsterm.completeNode.value, "no completion value after paste (ctrl-v)");
+
+ // using executeSoon() to get out of the webconsole event loop.
+ executeSoon(deferred.resolve);
+ });
+
+ // Get out of the webconsole event loop.
+ executeSoon(() => {
+ EventUtils.synthesizeKey("v", {accelKey: true});
+ });
+ }
+
+ info("wait for completion value after typing 'docu'");
+ jsterm.once("autocomplete-updated", onCompletionValue);
+
+ EventUtils.synthesizeKey("u", {});
+
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_autocomplete_crossdomain_iframe.js b/browser/devtools/webconsole/test/browser_webconsole_autocomplete_crossdomain_iframe.js
new file mode 100644
index 000000000..b5e603164
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_autocomplete_crossdomain_iframe.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that autocomplete doesn't break when trying to reach into objects from
+// a different domain, bug 989025.
+
+function test() {
+ let hud;
+
+ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-989025-iframe-parent.html";
+
+ Task.spawn(function*() {
+ const {tab} = yield loadTab(TEST_URI);
+ hud = yield openConsole(tab);
+
+ hud.jsterm.execute('document.title');
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "989025 - iframe parent",
+ category: CATEGORY_OUTPUT,
+ }],
+ });
+
+ let autocompleteUpdated = hud.jsterm.once("autocomplete-updated");
+
+ hud.jsterm.setInputValue("window[0].document");
+ executeSoon(() => {
+ EventUtils.synthesizeKey(".", {});
+ });
+
+ yield autocompleteUpdated;
+
+ hud.jsterm.setInputValue("window[0].document.title");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "Permission denied",
+ category: CATEGORY_OUTPUT,
+ severity: SEVERITY_ERROR,
+ }],
+ });
+
+ hud.jsterm.execute("window.location");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "test-bug-989025-iframe-parent.html",
+ category: CATEGORY_OUTPUT,
+ }],
+ });
+
+ yield closeConsole(tab);
+ }).then(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_autocomplete_in_debugger_stackframe.js b/browser/devtools/webconsole/test/browser_webconsole_autocomplete_in_debugger_stackframe.js
new file mode 100644
index 000000000..c3c838cb1
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_autocomplete_in_debugger_stackframe.js
@@ -0,0 +1,242 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that makes sure web console autocomplete happens in the user-selected stackframe
+// from the js debugger.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-autocomplete-in-stackframe.html";
+
+let testDriver, gStackframes;
+
+function test()
+{
+ requestLongerTimeout(2);
+ loadTab(TEST_URI).then(() => {
+ openConsole().then((hud) => {
+ testDriver = testCompletion(hud);
+ testDriver.next();
+ });
+ });
+}
+
+function testNext() {
+ executeSoon(function() {
+ testDriver.next();
+ });
+}
+
+function testCompletion(hud) {
+ let jsterm = hud.jsterm;
+ let input = jsterm.inputNode;
+ let popup = jsterm.autocompletePopup;
+
+ // Test that document.title gives string methods. Native getters must execute.
+ input.value = "document.title.";
+ input.setSelectionRange(input.value.length, input.value.length);
+ jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
+ yield undefined;
+
+ let newItems = popup.getItems();
+ ok(newItems.length > 0, "'document.title.' gave a list of suggestions");
+ ok(newItems.some(function(item) {
+ return item.label == "substr";
+ }), "autocomplete results do contain substr");
+ ok(newItems.some(function(item) {
+ return item.label == "toLowerCase";
+ }), "autocomplete results do contain toLowerCase");
+ ok(newItems.some(function(item) {
+ return item.label == "strike";
+ }), "autocomplete results do contain strike");
+
+ // Test if 'f' gives 'foo1' but not 'foo2' or 'foo3'
+ input.value = "f";
+ input.setSelectionRange(1, 1);
+ jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
+ yield undefined;
+
+ newItems = popup.getItems();
+ ok(newItems.length > 0, "'f' gave a list of suggestions");
+ ok(!newItems.every(function(item) {
+ return item.label != "foo1";
+ }), "autocomplete results do contain foo1");
+ ok(!newItems.every(function(item) {
+ return item.label != "foo1Obj";
+ }), "autocomplete results do contain foo1Obj");
+ ok(newItems.every(function(item) {
+ return item.label != "foo2";
+ }), "autocomplete results do not contain foo2");
+ ok(newItems.every(function(item) {
+ return item.label != "foo2Obj";
+ }), "autocomplete results do not contain foo2Obj");
+ ok(newItems.every(function(item) {
+ return item.label != "foo3";
+ }), "autocomplete results do not contain foo3");
+ ok(newItems.every(function(item) {
+ return item.label != "foo3Obj";
+ }), "autocomplete results do not contain foo3Obj");
+
+ // Test if 'foo1Obj.' gives 'prop1' and 'prop2'
+ input.value = "foo1Obj.";
+ input.setSelectionRange(8, 8);
+ jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
+ yield undefined;
+
+ newItems = popup.getItems();
+ ok(!newItems.every(function(item) {
+ return item.label != "prop1";
+ }), "autocomplete results do contain prop1");
+ ok(!newItems.every(function(item) {
+ return item.label != "prop2";
+ }), "autocomplete results do contain prop2");
+
+ // Test if 'foo1Obj.prop2.' gives 'prop21'
+ input.value = "foo1Obj.prop2.";
+ input.setSelectionRange(14, 14);
+ jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
+ yield undefined;
+
+ newItems = popup.getItems();
+ ok(!newItems.every(function(item) {
+ return item.label != "prop21";
+ }), "autocomplete results do contain prop21");
+
+ info("openDebugger");
+ executeSoon(() => openDebugger().then(debuggerOpened));
+ yield undefined;
+
+ // From this point on the
+ // Test if 'f' gives 'foo3' and 'foo1' but not 'foo2'
+ input.value = "f";
+ input.setSelectionRange(1, 1);
+ jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
+ yield undefined;
+
+ newItems = popup.getItems();
+ ok(newItems.length > 0, "'f' gave a list of suggestions");
+ ok(!newItems.every(function(item) {
+ return item.label != "foo3";
+ }), "autocomplete results do contain foo3");
+ ok(!newItems.every(function(item) {
+ return item.label != "foo3Obj";
+ }), "autocomplete results do contain foo3Obj");
+ ok(!newItems.every(function(item) {
+ return item.label != "foo1";
+ }), "autocomplete results do contain foo1");
+ ok(!newItems.every(function(item) {
+ return item.label != "foo1Obj";
+ }), "autocomplete results do contain foo1Obj");
+ ok(newItems.every(function(item) {
+ return item.label != "foo2";
+ }), "autocomplete results do not contain foo2");
+ ok(newItems.every(function(item) {
+ return item.label != "foo2Obj";
+ }), "autocomplete results do not contain foo2Obj");
+
+ openDebugger().then(() => {
+ gStackframes.selectFrame(1);
+
+ info("openConsole");
+ executeSoon(() => openConsole().then(() => testDriver.next()));
+ });
+ yield undefined;
+
+ // Test if 'f' gives 'foo2' and 'foo1' but not 'foo3'
+ input.value = "f";
+ input.setSelectionRange(1, 1);
+ jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
+ yield undefined;
+
+ newItems = popup.getItems();
+ ok(newItems.length > 0, "'f' gave a list of suggestions");
+ ok(!newItems.every(function(item) {
+ return item.label != "foo2";
+ }), "autocomplete results do contain foo2");
+ ok(!newItems.every(function(item) {
+ return item.label != "foo2Obj";
+ }), "autocomplete results do contain foo2Obj");
+ ok(!newItems.every(function(item) {
+ return item.label != "foo1";
+ }), "autocomplete results do contain foo1");
+ ok(!newItems.every(function(item) {
+ return item.label != "foo1Obj";
+ }), "autocomplete results do contain foo1Obj");
+ ok(newItems.every(function(item) {
+ return item.label != "foo3";
+ }), "autocomplete results do not contain foo3");
+ ok(newItems.every(function(item) {
+ return item.label != "foo3Obj";
+ }), "autocomplete results do not contain foo3Obj");
+
+ // Test if 'foo2Obj.' gives 'prop1'
+ input.value = "foo2Obj.";
+ input.setSelectionRange(8, 8);
+ jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
+ yield undefined;
+
+ newItems = popup.getItems();
+ ok(!newItems.every(function(item) {
+ return item.label != "prop1";
+ }), "autocomplete results do contain prop1");
+
+ // Test if 'foo2Obj.prop1.' gives 'prop11'
+ input.value = "foo2Obj.prop1.";
+ input.setSelectionRange(14, 14);
+ jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
+ yield undefined;
+
+ newItems = popup.getItems();
+ ok(!newItems.every(function(item) {
+ return item.label != "prop11";
+ }), "autocomplete results do contain prop11");
+
+ // Test if 'foo2Obj.prop1.prop11.' gives suggestions for a string i.e. 'length'
+ input.value = "foo2Obj.prop1.prop11.";
+ input.setSelectionRange(21, 21);
+ jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
+ yield undefined;
+
+ newItems = popup.getItems();
+ ok(!newItems.every(function(item) {
+ return item.label != "length";
+ }), "autocomplete results do contain length");
+
+ // Test if 'foo1Obj[0].' throws no errors.
+ input.value = "foo2Obj[0].";
+ input.setSelectionRange(11, 11);
+ jsterm.complete(jsterm.COMPLETE_HINT_ONLY, testNext);
+ yield undefined;
+
+ newItems = popup.getItems();
+ is(newItems.length, 0, "no items for foo2Obj[0]");
+
+ testDriver = null;
+ executeSoon(finishUp);
+ yield undefined;
+}
+
+function debuggerOpened(aResult)
+{
+ let debuggerWin = aResult.panelWin;
+ let debuggerController = debuggerWin.DebuggerController;
+ let thread = debuggerController.activeThread;
+ gStackframes = debuggerController.StackFrames;
+
+ executeSoon(() => {
+ thread.addOneTimeListener("framesadded", onFramesAdded);
+ info("firstCall()");
+ content.wrappedJSObject.firstCall();
+ });
+}
+
+function onFramesAdded()
+{
+ info("onFramesAdded, openConsole() now");
+ executeSoon(() => openConsole().then(testNext));
+}
+
+function finishUp() {
+ testDriver = gStackframes = null;
+ finishTest();
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_autocomplete_popup_close_on_tab_switch.js b/browser/devtools/webconsole/test/browser_webconsole_autocomplete_popup_close_on_tab_switch.js
new file mode 100644
index 000000000..19c3ceb49
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_autocomplete_popup_close_on_tab_switch.js
@@ -0,0 +1,33 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 that the autocomplete popup closes on switching tabs. See bug 900448.
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>bug 900448 - autocomplete popup closes on tab switch";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ let popup = hud.jsterm.autocompletePopup;
+ let popupShown = onPopupShown(popup._panel);
+
+ hud.jsterm.setInputValue("sc");
+ EventUtils.synthesizeKey("r", {});
+
+ yield popupShown;
+
+ ok(!popup.isOpen, "Popup closes on tab switch");
+});
+
+function onPopupShown(panel) {
+ let finished = promise.defer();
+
+ panel.addEventListener("popupshown", function popupOpened() {
+ panel.removeEventListener("popupshown", popupOpened, false);
+ loadTab("data:text/html;charset=utf-8,<p>testing autocomplete closes").then(finished.resolve);
+ }, false);
+
+ return finished.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_basic_net_logging.js b/browser/devtools/webconsole/test/browser_webconsole_basic_net_logging.js
new file mode 100644
index 000000000..c3c8005d4
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_basic_net_logging.js
@@ -0,0 +1,42 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that the page's resources are displayed in the console as they're
+// loaded
+
+"use strict";
+
+const TEST_NETWORK_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-network.html" + "?_date=" + Date.now();
+
+let test = asyncTest(function* () {
+ yield loadTab("data:text/html;charset=utf-8,Web Console basic network logging test");
+ let hud = yield openConsole();
+
+ content.location = TEST_NETWORK_URI;
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "running network console",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "test-network.html",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "testscript.js",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "test-image.png",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ }],
+ });
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_block_mixedcontent_securityerrors.js b/browser/devtools/webconsole/test/browser_webconsole_block_mixedcontent_securityerrors.js
new file mode 100644
index 000000000..47e5cc549
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_block_mixedcontent_securityerrors.js
@@ -0,0 +1,122 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// The test loads a web page with mixed active and display content
+// on it while the "block mixed content" settings are _on_.
+// It then checks that the blocked mixed content warning messages
+// are logged to the console and have the correct "Learn More"
+// url appended to them. After the first test finishes, it invokes
+// a second test that overrides the mixed content blocker settings
+// by clicking on the doorhanger shield and validates that the
+// appropriate messages are logged to console.
+// Bug 875456 - Log mixed content messages from the Mixed Content
+// Blocker to the Security Pane in the Web Console
+
+const TEST_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-mixedcontent-securityerrors.html";
+const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/MixedContent";
+
+
+let test = asyncTest(function* () {
+ yield pushPrefEnv();
+
+ let { browser } = yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ let results = yield waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "Logged blocking mixed active content",
+ text: "Blocked loading mixed active content \"http://example.com/\"",
+ category: CATEGORY_SECURITY,
+ severity: SEVERITY_ERROR,
+ objects: true,
+ },
+ {
+ name: "Logged blocking mixed passive content - image",
+ text: "Blocked loading mixed active content \"http://example.com/\"",
+ category: CATEGORY_SECURITY,
+ severity: SEVERITY_ERROR,
+ objects: true,
+ },
+ ],
+ });
+
+ yield testClickOpenNewTab(hud, results[0]);
+
+ let results2 = yield mixedContentOverrideTest2(hud, browser);
+
+ yield testClickOpenNewTab(hud, results2[0]);
+});
+
+function pushPrefEnv()
+{
+ let deferred = promise.defer();
+ let options = {"set": [["security.mixed_content.block_active_content", true],
+ ["security.mixed_content.block_display_content", true]]};
+ SpecialPowers.pushPrefEnv(options, deferred.resolve);
+ return deferred.promise;
+}
+
+function waitForNotificationShown(notification, callback)
+{
+ if (PopupNotifications.panel.state == "open") {
+ executeSoon(callback);
+ return;
+ }
+ PopupNotifications.panel.addEventListener("popupshown", function onShown(e) {
+ PopupNotifications.panel.removeEventListener("popupshown", onShown);
+ callback();
+ }, false);
+ notification.reshow();
+}
+
+function mixedContentOverrideTest2(hud, browser)
+{
+ var notification = PopupNotifications.getNotification("bad-content", browser);
+ ok(notification, "Mixed Content Doorhanger did appear");
+ let deferred = promise.defer();
+ waitForNotificationShown(notification, () => {
+ afterNotificationShown(hud, notification, deferred);
+ });
+ return deferred.promise;
+}
+
+function afterNotificationShown(hud, notification, deferred)
+{
+ ok(PopupNotifications.panel.firstChild.isMixedContentBlocked, "OK: Mixed Content is being blocked");
+ // Click on the doorhanger.
+ PopupNotifications.panel.firstChild.disableMixedContentProtection();
+ notification.remove();
+
+ waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "Logged blocking mixed active content",
+ text: "Loading mixed (insecure) active content \"http://example.com/\"" +
+ " on a secure page",
+ category: CATEGORY_SECURITY,
+ severity: SEVERITY_WARNING,
+ objects: true,
+ },
+ {
+ name: "Logged blocking mixed passive content - image",
+ text: "Loading mixed (insecure) display content" +
+ " \"http://example.com/tests/image/test/mochitest/blue.png\"" +
+ " on a secure page",
+ category: CATEGORY_SECURITY,
+ severity: SEVERITY_WARNING,
+ objects: true,
+ },
+ ],
+ }).then(msgs => deferred.resolve(msgs), Cu.reportError);
+}
+
+function testClickOpenNewTab(hud, match) {
+ let warningNode = match.clickableElements[0];
+ ok(warningNode, "link element");
+ ok(warningNode.classList.contains("learn-more-link"), "link class name");
+ return simulateMessageLinkClick(warningNode, LEARN_MORE_URI);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_1006027_message_timestamps_incorrect.js b/browser/devtools/webconsole/test/browser_webconsole_bug_1006027_message_timestamps_incorrect.js
new file mode 100644
index 000000000..6dd85e939
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_1006027_message_timestamps_incorrect.js
@@ -0,0 +1,41 @@
+/* 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/. */
+
+"use strict";
+
+function test() {
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ const {tab} = yield loadTab("data:text/html;charset=utf8,<title>Test for Bug 1006027");
+
+ const target = TargetFactory.forTab(tab);
+ const hud = yield openConsole(tab);
+
+ hud.jsterm.execute("console.log('bug1006027')");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "console.log",
+ text: "bug1006027",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ info('hud.outputNode.textContent:\n'+hud.outputNode.textContent);
+ let timestampNodes = hud.outputNode.querySelectorAll('span.timestamp');
+ let aTimestampMilliseconds = Array.prototype.map.call(timestampNodes,
+ function (value) {
+ // We are parsing timestamps as local time, relative to the begin of the epoch.
+ // This is not the correct value of the timestamp, but good enough for comparison.
+ return Date.parse('T'+String.trim(value.textContent));
+ });
+
+ let minTimestamp = Math.min.apply(null, aTimestampMilliseconds);
+ let maxTimestamp = Math.max.apply(null, aTimestampMilliseconds);
+ ok(Math.abs(maxTimestamp - minTimestamp) < 1000, "console.log message timestamp spread < 1000ms confirmed");
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_1010953_cspro.js b/browser/devtools/webconsole/test/browser_webconsole_bug_1010953_cspro.js
new file mode 100644
index 000000000..078f44b74
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_1010953_cspro.js
@@ -0,0 +1,47 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/* We are loading:
+a script that is allowed by the CSP header but not by the CSPRO header
+an image which is allowed by the CSPRO header but not by the CSP header.
+
+So we expect a warning (image has been blocked) and a report
+ (script should not load and was reported)
+
+The expected console messages in the constants CSP_VIOLATION_MSG and CSP_REPORT_MSG are confirmed to be found in the console messages.
+*/
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf8,Web Console CSP report only test (bug 1010953)";
+const TEST_VIOLATION = "http://example.com/browser/browser/devtools/webconsole/test/test_bug_1010953_cspro.html";
+const CSP_VIOLATION_MSG = 'Content Security Policy: The page\'s settings blocked the loading of a resource at http://some.example.com/test.png ("img-src http://example.com").';
+const CSP_REPORT_MSG = 'Content Security Policy: The page\'s settings observed the loading of a resource at http://some.example.com/test_bug_1010953_cspro.js ("script-src http://example.com"). A CSP report is being sent.';
+
+let test = asyncTest(function* () {
+ let { browser } = yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ hud.jsterm.clearOutput();
+
+ let loaded = loadBrowser(browser);
+ content.location = TEST_VIOLATION;
+ yield loaded;
+
+ let aOutputNode = hud.outputNode;
+
+ yield waitForSuccess({
+ name: "Confirmed that CSP and CSP-Report-Only log different messages to the console.",
+ validator: function() {
+ console.log(hud.outputNode.textContent);
+ let success = false;
+ success = hud.outputNode.textContent.indexOf(CSP_VIOLATION_MSG) > -1 &&
+ hud.outputNode.textContent.indexOf(CSP_REPORT_MSG) > -1;
+ return success;
+ }
+ });
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_578437_page_reload.js b/browser/devtools/webconsole/test/browser_webconsole_bug_578437_page_reload.js
new file mode 100644
index 000000000..f396259a5
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_578437_page_reload.js
@@ -0,0 +1,39 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that the console object still exists after a page reload.
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let browser;
+
+function test() {
+ loadTab(TEST_URI).then({
+ openConsole().then((tab) => {
+ browser = tab.browser;
+
+ browser.addEventListener("DOMContentLoaded", testPageReload, false);
+ content.location.reload();
+ });
+ });
+ browser.addEventListener("DOMContentLoaded", onLoad, false);
+}
+
+function testPageReload() {
+
+ browser.removeEventListener("DOMContentLoaded", testPageReload, false);
+
+ let console = browser.contentWindow.wrappedJSObject.console;
+
+ is(typeof console, "object", "window.console is an object, after page reload");
+ is(typeof console.log, "function", "console.log is a function");
+ is(typeof console.info, "function", "console.info is a function");
+ is(typeof console.warn, "function", "console.warn is a function");
+ is(typeof console.error, "function", "console.error is a function");
+ is(typeof console.exception, "function", "console.exception is a function");
+
+ browser = null;
+ finishTest();
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_579412_input_focus.js b/browser/devtools/webconsole/test/browser_webconsole_bug_579412_input_focus.js
new file mode 100644
index 000000000..e726a0595
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_579412_input_focus.js
@@ -0,0 +1,19 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that the input field is focused when the console is opened.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ hud.jsterm.clearOutput();
+
+ let inputNode = hud.jsterm.inputNode;
+ ok(inputNode.getAttribute("focused"), "input node is focused");
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_580001_closing_after_completion.js b/browser/devtools/webconsole/test/browser_webconsole_bug_580001_closing_after_completion.js
new file mode 100644
index 000000000..b71d83bd4
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_580001_closing_after_completion.js
@@ -0,0 +1,48 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests to ensure that errors don't appear when the console is closed while a
+// completion is being performed.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+
+let test = asyncTest(function* () {
+ let { browser } = yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+ yield testClosingAfterCompletion(hud, browser);
+});
+
+function testClosingAfterCompletion(hud, browser) {
+ let deferred = promise.defer();
+
+ let inputNode = hud.jsterm.inputNode;
+
+ let errorWhileClosing = false;
+ function errorListener(evt) {
+ errorWhileClosing = true;
+ }
+
+ browser.addEventListener("error", errorListener, false);
+
+ // Focus the inputNode and perform the keycombo to close the WebConsole.
+ inputNode.focus();
+
+ gDevTools.once("toolbox-destroyed", function() {
+ browser.removeEventListener("error", errorListener, false);
+ is(errorWhileClosing, false, "no error while closing the WebConsole");
+ deferred.resolve();
+ });
+
+ if (Services.appinfo.OS == "Darwin") {
+ EventUtils.synthesizeKey("i", { accelKey: true, altKey: true });
+ } else {
+ EventUtils.synthesizeKey("i", { accelKey: true, shiftKey: true });
+ }
+
+ return deferred.promise;
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_580030_errors_after_page_reload.js b/browser/devtools/webconsole/test/browser_webconsole_bug_580030_errors_after_page_reload.js
new file mode 100644
index 000000000..de208ee06
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_580030_errors_after_page_reload.js
@@ -0,0 +1,42 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that errors still show up in the Web Console after a page reload.
+// See bug 580030: the error handler fails silently after page reload.
+// https://bugzilla.mozilla.org/show_bug.cgi?id=580030
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-error.html";
+
+function test() {
+ Task.spawn(function*() {
+ const {tab} = yield loadTab(TEST_URI);
+ const hud = yield openConsole(tab);
+ info("console opened");
+
+ executeSoon(() => {
+ hud.jsterm.clearOutput();
+ info("wait for reload");
+ content.location.reload();
+ });
+
+ yield hud.target.once("navigate");
+ info("target navigated");
+
+ let button = content.document.querySelector("button");
+ ok(button, "button found");
+
+ expectUncaughtException();
+ EventUtils.sendMouseEvent({type: "click"}, button, content);
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "fooBazBaz is not defined",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ }],
+ });
+ }).then(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_580454_timestamp_l10n.js b/browser/devtools/webconsole/test/browser_webconsole_bug_580454_timestamp_l10n.js
new file mode 100644
index 000000000..b2887ddae
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_580454_timestamp_l10n.js
@@ -0,0 +1,30 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Patrick Walton <pcwalton@mozilla.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Tests that appropriately-localized timestamps are printed.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ const TEST_TIMESTAMP = 12345678;
+ let date = new Date(TEST_TIMESTAMP);
+ let localizedString = WCU_l10n.timestampString(TEST_TIMESTAMP);
+ isnot(localizedString.indexOf(date.getHours()), -1, "the localized " +
+ "timestamp contains the hours");
+ isnot(localizedString.indexOf(date.getMinutes()), -1, "the localized " +
+ "timestamp contains the minutes");
+ isnot(localizedString.indexOf(date.getSeconds()), -1, "the localized " +
+ "timestamp contains the seconds");
+ isnot(localizedString.indexOf(date.getMilliseconds()), -1, "the localized " +
+ "timestamp contains the milliseconds");
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_582201_duplicate_errors.js b/browser/devtools/webconsole/test/browser_webconsole_bug_582201_duplicate_errors.js
new file mode 100644
index 000000000..6032cb761
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_582201_duplicate_errors.js
@@ -0,0 +1,44 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that exceptions thrown by content don't show up twice in the Web
+// Console.
+
+"use strict";
+
+const INIT_URI = "data:text/html;charset=utf8,hello world";
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-duplicate-error.html";
+
+let test = asyncTest(function* () {
+ yield loadTab(INIT_URI);
+
+ let hud = yield openConsole();
+
+ expectUncaughtException();
+
+ content.location = TEST_URI;
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "fooDuplicateError1",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ },
+ {
+ text: "test-duplicate-error.html",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ let text = hud.outputNode.textContent;
+ let error1pos = text.indexOf("fooDuplicateError1");
+ ok(error1pos > -1, "found fooDuplicateError1");
+ if (error1pos > -1) {
+ ok(text.indexOf("fooDuplicateError1", error1pos + 1) == -1,
+ "no duplicate for fooDuplicateError1");
+ }
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_583816_No_input_and_Tab_key_pressed.js b/browser/devtools/webconsole/test/browser_webconsole_bug_583816_No_input_and_Tab_key_pressed.js
new file mode 100644
index 000000000..1f089ad2e
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_583816_No_input_and_Tab_key_pressed.js
@@ -0,0 +1,31 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/browser/test-console.html";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+ testCompletion(hud);
+});
+
+function testCompletion(hud) {
+ var jsterm = hud.jsterm;
+ var input = jsterm.inputNode;
+
+ jsterm.setInputValue("");
+ EventUtils.synthesizeKey("VK_TAB", {});
+ is(jsterm.completeNode.value, "<- no result", "<- no result - matched");
+ is(input.value, "", "inputnode is empty - matched")
+ is(input.getAttribute("focused"), "true", "input is still focused");
+
+ //Any thing which is not in property autocompleter
+ jsterm.setInputValue("window.Bug583816");
+ EventUtils.synthesizeKey("VK_TAB", {});
+ is(jsterm.completeNode.value, " <- no result", "completenode content - matched");
+ is(input.value, "window.Bug583816", "inputnode content - matched");
+ is(input.getAttribute("focused"), "true", "input is still focused");
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_585237_line_limit.js b/browser/devtools/webconsole/test/browser_webconsole_bug_585237_line_limit.js
new file mode 100644
index 000000000..9ec4291e3
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_585237_line_limit.js
@@ -0,0 +1,87 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Patrick Walton <pcwalton@mozilla.com>
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Tests that the Web Console limits the number of lines displayed according to
+// the user's preferences.
+
+const TEST_URI = "data:text/html;charset=utf8,test for bug 585237";
+
+let outputNode;
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ let console = content.console;
+ outputNode = hud.outputNode;
+
+ hud.jsterm.clearOutput();
+
+ let prefBranch = Services.prefs.getBranch("devtools.hud.loglimit.");
+ prefBranch.setIntPref("console", 20);
+
+ for (let i = 0; i < 30; i++) {
+ console.log("foo #" + i); // must change message to prevent repeats
+ }
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "foo #29",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ is(countMessageNodes(), 20, "there are 20 message nodes in the output " +
+ "when the log limit is set to 20");
+
+ console.log("bar bug585237");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "bar bug585237",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ is(countMessageNodes(), 20, "there are still 20 message nodes in the " +
+ "output when adding one more");
+
+ prefBranch.setIntPref("console", 30);
+ for (let i = 0; i < 20; i++) {
+ console.log("boo #" + i); // must change message to prevent repeats
+ }
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "boo #19",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ is(countMessageNodes(), 30, "there are 30 message nodes in the output " +
+ "when the log limit is set to 30");
+
+ prefBranch.clearUserPref("console");
+
+ outputNode = null;
+});
+
+function countMessageNodes() {
+ return outputNode.querySelectorAll(".message").length;
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_585956_console_trace.js b/browser/devtools/webconsole/test/browser_webconsole_bug_585956_console_trace.js
new file mode 100644
index 000000000..8945d2fa6
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_585956_console_trace.js
@@ -0,0 +1,48 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-585956-console-trace.html";
+
+function test() {
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ let {tab} = yield loadTab("data:text/html;charset=utf8,<p>hello");
+ let hud = yield openConsole(tab);
+
+ content.location = TEST_URI;
+
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "console.trace output",
+ consoleTrace: {
+ file: "test-bug-585956-console-trace.html",
+ fn: "window.foobar585956c",
+ },
+ }],
+ });
+
+ let node = [...result.matched][0];
+ ok(node, "found trace log node");
+
+ let obj = node._messageObject;
+ ok(obj, "console.trace message object");
+
+ // The expected stack trace object.
+ let stacktrace = [
+ { columnNumber: 2, filename: TEST_URI, functionName: "window.foobar585956c", language: 2, lineNumber: 9 },
+ { columnNumber: 9, filename: TEST_URI, functionName: "foobar585956b", language: 2, lineNumber: 14 },
+ { columnNumber: 9, filename: TEST_URI, functionName: "foobar585956a", language: 2, lineNumber: 18 },
+ { columnNumber: 0, filename: TEST_URI, functionName: "", language: 2, lineNumber: 21 }
+ ];
+
+ ok(obj._stacktrace, "found stacktrace object");
+ is(obj._stacktrace.toSource(), stacktrace.toSource(), "stacktrace is correct");
+ isnot(node.textContent.indexOf("bug-585956"), -1, "found file name");
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js b/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js
new file mode 100644
index 000000000..7704bd627
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_keys.js
@@ -0,0 +1,376 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>bug 585991 - autocomplete popup keyboard usage test";
+let HUD, popup, jsterm, inputNode, completeNode;
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+
+ yield consoleOpened(hud);
+ yield popupHideAfterTab();
+ yield testReturnKey();
+ yield dontShowArrayNumbers();
+ yield testReturnWithNoSelection();
+ yield popupHideAfterReturnWithNoSelection();
+ yield testCompletionInText();
+ yield popupHideAfterCompletionInText();
+
+ HUD = popup = jsterm = inputNode = completeNode = null;
+});
+
+let consoleOpened = Task.async(function*(aHud) {
+ let deferred = promise.defer();
+ HUD = aHud;
+ info("web console opened");
+
+ jsterm = HUD.jsterm;
+
+ yield jsterm.execute("window.foobarBug585991={" +
+ "'item0': 'value0'," +
+ "'item1': 'value1'," +
+ "'item2': 'value2'," +
+ "'item3': 'value3'" +
+ "}");
+ yield jsterm.execute("window.testBug873250a = 'hello world';"
+ + "window.testBug873250b = 'hello world 2';");
+ popup = jsterm.autocompletePopup;
+ completeNode = jsterm.completeNode;
+ inputNode = jsterm.inputNode;
+
+ ok(!popup.isOpen, "popup is not open");
+
+ popup._panel.addEventListener("popupshown", function onShown() {
+ popup._panel.removeEventListener("popupshown", onShown, false);
+
+ ok(popup.isOpen, "popup is open");
+
+ // 4 values, and the following properties:
+ // __defineGetter__ __defineSetter__ __lookupGetter__ __lookupSetter__
+ // hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString
+ // toSource unwatch valueOf watch constructor.
+ is(popup.itemCount, 18, "popup.itemCount is correct");
+
+ let sameItems = popup.getItems().reverse().map(function(e) {return e.label;});
+ ok(sameItems.every(function(prop, index) {
+ return [
+ "__defineGetter__",
+ "__defineSetter__",
+ "__lookupGetter__",
+ "__lookupSetter__",
+ "constructor",
+ "hasOwnProperty",
+ "isPrototypeOf",
+ "item0",
+ "item1",
+ "item2",
+ "item3",
+ "propertyIsEnumerable",
+ "toLocaleString",
+ "toSource",
+ "toString",
+ "unwatch",
+ "valueOf",
+ "watch",
+ ][index] === prop}), "getItems returns the items we expect");
+
+ is(popup.selectedIndex, 17,
+ "Index of the first item from bottom is selected.");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
+
+ is(popup.selectedIndex, 0, "index 0 is selected");
+ is(popup.selectedItem.label, "watch", "watch is selected");
+ is(completeNode.value, prefix + "watch",
+ "completeNode.value holds watch");
+
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ is(popup.selectedIndex, 1, "index 1 is selected");
+ is(popup.selectedItem.label, "valueOf", "valueOf is selected");
+ is(completeNode.value, prefix + "valueOf",
+ "completeNode.value holds valueOf");
+
+ EventUtils.synthesizeKey("VK_UP", {});
+
+ is(popup.selectedIndex, 0, "index 0 is selected");
+ is(popup.selectedItem.label, "watch", "watch is selected");
+ is(completeNode.value, prefix + "watch",
+ "completeNode.value holds watch");
+
+ let currentSelectionIndex = popup.selectedIndex;
+
+ EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
+
+ ok(popup.selectedIndex > currentSelectionIndex,
+ "Index is greater after PGDN");
+
+ currentSelectionIndex = popup.selectedIndex;
+ EventUtils.synthesizeKey("VK_PAGE_UP", {});
+
+ ok(popup.selectedIndex < currentSelectionIndex, "Index is less after Page UP");
+
+ EventUtils.synthesizeKey("VK_END", {});
+ is(popup.selectedIndex, 17, "index is last after End");
+
+ EventUtils.synthesizeKey("VK_HOME", {});
+ is(popup.selectedIndex, 0, "index is first after Home");
+
+ info("press Tab and wait for popup to hide");
+ popup._panel.addEventListener("popuphidden", function popupHidden() {
+ popup._panel.removeEventListener("popuphidden", popupHidden, false);
+ deferred.resolve();
+ }, false);
+ EventUtils.synthesizeKey("VK_TAB", {});
+ }, false);
+
+ info("wait for completion: window.foobarBug585991.");
+ jsterm.setInputValue("window.foobarBug585991");
+ EventUtils.synthesizeKey(".", {});
+
+ return deferred.promise;
+});
+
+function popupHideAfterTab()
+{
+ let deferred = promise.defer();
+
+ // At this point the completion suggestion should be accepted.
+ ok(!popup.isOpen, "popup is not open");
+
+ is(inputNode.value, "window.foobarBug585991.watch",
+ "completion was successful after VK_TAB");
+
+ ok(!completeNode.value, "completeNode is empty");
+
+ popup._panel.addEventListener("popupshown", function onShown() {
+ popup._panel.removeEventListener("popupshown", onShown, false);
+
+ ok(popup.isOpen, "popup is open");
+
+ is(popup.itemCount, 18, "popup.itemCount is correct");
+
+ is(popup.selectedIndex, 17, "First index from bottom is selected");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
+
+ is(popup.selectedIndex, 0, "index 0 is selected");
+ is(popup.selectedItem.label, "watch", "watch is selected");
+ is(completeNode.value, prefix + "watch",
+ "completeNode.value holds watch");
+
+ popup._panel.addEventListener("popuphidden", function onHidden() {
+ popup._panel.removeEventListener("popuphidden", onHidden, false);
+
+ ok(!popup.isOpen, "popup is not open after VK_ESCAPE");
+
+ is(inputNode.value, "window.foobarBug585991.",
+ "completion was cancelled");
+
+ ok(!completeNode.value, "completeNode is empty");
+
+ deferred.resolve();
+ }, false);
+
+ info("press Escape to close the popup");
+ executeSoon(function() {
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ });
+ }, false);
+
+ info("wait for completion: window.foobarBug585991.");
+ executeSoon(function() {
+ jsterm.setInputValue("window.foobarBug585991");
+ EventUtils.synthesizeKey(".", {});
+ });
+
+ return deferred.promise;
+}
+
+function testReturnKey()
+{
+ let deferred = promise.defer();
+
+ popup._panel.addEventListener("popupshown", function onShown() {
+ popup._panel.removeEventListener("popupshown", onShown, false);
+
+ ok(popup.isOpen, "popup is open");
+
+ is(popup.itemCount, 18, "popup.itemCount is correct");
+
+ is(popup.selectedIndex, 17, "First index from bottom is selected");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ let prefix = jsterm.inputNode.value.replace(/[\S]/g, " ");
+
+ is(popup.selectedIndex, 0, "index 0 is selected");
+ is(popup.selectedItem.label, "watch", "watch is selected");
+ is(completeNode.value, prefix + "watch",
+ "completeNode.value holds watch");
+
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ is(popup.selectedIndex, 1, "index 1 is selected");
+ is(popup.selectedItem.label, "valueOf", "valueOf is selected");
+ is(completeNode.value, prefix + "valueOf",
+ "completeNode.value holds valueOf");
+
+ popup._panel.addEventListener("popuphidden", function onHidden() {
+ popup._panel.removeEventListener("popuphidden", onHidden, false);
+
+ ok(!popup.isOpen, "popup is not open after VK_RETURN");
+
+ is(inputNode.value, "window.foobarBug585991.valueOf",
+ "completion was successful after VK_RETURN");
+
+ ok(!completeNode.value, "completeNode is empty");
+
+ deferred.resolve();
+ }, false);
+
+ info("press Return to accept suggestion. wait for popup to hide");
+
+ executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
+ }, false);
+
+ info("wait for completion suggestions: window.foobarBug585991.");
+
+ executeSoon(function() {
+ jsterm.setInputValue("window.foobarBug58599");
+ EventUtils.synthesizeKey("1", {});
+ EventUtils.synthesizeKey(".", {});
+ });
+
+ return deferred.promise;
+}
+
+function dontShowArrayNumbers()
+{
+ let deferred = promise.defer();
+
+ info("dontShowArrayNumbers");
+ content.wrappedJSObject.foobarBug585991 = ["Sherlock Holmes"];
+
+ let jsterm = HUD.jsterm;
+ let popup = jsterm.autocompletePopup;
+ let completeNode = jsterm.completeNode;
+
+ popup._panel.addEventListener("popupshown", function onShown() {
+ popup._panel.removeEventListener("popupshown", onShown, false);
+
+ let sameItems = popup.getItems().map(function(e) {return e.label;});
+ ok(!sameItems.some(function(prop, index) { prop === "0"; }),
+ "Completing on an array doesn't show numbers.");
+
+ popup._panel.addEventListener("popuphidden", function popupHidden() {
+ popup._panel.removeEventListener("popuphidden", popupHidden, false);
+ deferred.resolve();
+ }, false);
+
+ info("wait for popup to hide");
+ executeSoon(() => EventUtils.synthesizeKey("VK_ESCAPE", {}));
+ }, false);
+
+ info("wait for popup to show");
+ executeSoon(() => {
+ jsterm.setInputValue("window.foobarBug585991");
+ EventUtils.synthesizeKey(".", {});
+ });
+
+ return deferred.promise;
+}
+
+function testReturnWithNoSelection()
+{
+ let deferred = promise.defer();
+
+ info("test pressing return with open popup, but no selection, see bug 873250");
+
+ popup._panel.addEventListener("popupshown", function onShown() {
+ popup._panel.removeEventListener("popupshown", onShown);
+
+ ok(popup.isOpen, "popup is open");
+ is(popup.itemCount, 2, "popup.itemCount is correct");
+ isnot(popup.selectedIndex, -1, "popup.selectedIndex is correct");
+
+ info("press Return and wait for popup to hide");
+ popup._panel.addEventListener("popuphidden", function popupHidden() {
+ popup._panel.removeEventListener("popuphidden", popupHidden);
+ deferred.resolve();
+ });
+ executeSoon(() => EventUtils.synthesizeKey("VK_RETURN", {}));
+ });
+
+ executeSoon(() => {
+ info("wait for popup to show");
+ jsterm.setInputValue("window.testBu");
+ EventUtils.synthesizeKey("g", {});
+ });
+
+ return deferred.promise;
+}
+
+function popupHideAfterReturnWithNoSelection()
+{
+ ok(!popup.isOpen, "popup is not open after VK_RETURN");
+
+ is(inputNode.value, "", "inputNode is empty after VK_RETURN");
+ is(completeNode.value, "", "completeNode is empty");
+ is(jsterm.history[jsterm.history.length-1], "window.testBug",
+ "jsterm history is correct");
+
+ return promise.resolve();
+}
+
+function testCompletionInText()
+{
+ info("test that completion works inside text, see bug 812618");
+
+ let deferred = promise.defer();
+
+ popup._panel.addEventListener("popupshown", function onShown() {
+ popup._panel.removeEventListener("popupshown", onShown);
+
+ ok(popup.isOpen, "popup is open");
+ is(popup.itemCount, 2, "popup.itemCount is correct");
+
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(popup.selectedIndex, 0, "popup.selectedIndex is correct");
+ ok(!completeNode.value, "completeNode.value is empty");
+
+ let items = popup.getItems().reverse().map(e => e.label);
+ let sameItems = items.every((prop, index) =>
+ ["testBug873250a", "testBug873250b"][index] === prop);
+ ok(sameItems, "getItems returns the items we expect");
+
+ info("press Tab and wait for popup to hide");
+ popup._panel.addEventListener("popuphidden", function popupHidden() {
+ popup._panel.removeEventListener("popuphidden", popupHidden);
+ deferred.resolve();
+ });
+ EventUtils.synthesizeKey("VK_TAB", {});
+ });
+
+ jsterm.setInputValue("dump(window.testBu)");
+ inputNode.selectionStart = inputNode.selectionEnd = 18;
+ EventUtils.synthesizeKey("g", {});
+ return deferred.promise;
+}
+
+function popupHideAfterCompletionInText()
+{
+ // At this point the completion suggestion should be accepted.
+ ok(!popup.isOpen, "popup is not open");
+ is(inputNode.value, "dump(window.testBug873250b)",
+ "completion was successful after VK_TAB");
+ is(inputNode.selectionStart, 26, "cursor location is correct");
+ is(inputNode.selectionStart, inputNode.selectionEnd, "cursor location (confirmed)");
+ ok(!completeNode.value, "completeNode is empty");
+
+ return promise.resolve();
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_popup.js b/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_popup.js
new file mode 100644
index 000000000..76fc5d324
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_585991_autocomplete_popup.js
@@ -0,0 +1,119 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>bug 585991 - autocomplete popup test";
+
+"use strict";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+
+ yield consoleOpened(hud);
+});
+
+function consoleOpened(HUD) {
+ let deferred = promise.defer();
+
+ let items = [
+ {label: "item0", value: "value0"},
+ {label: "item1", value: "value1"},
+ {label: "item2", value: "value2"},
+ ];
+
+ let popup = HUD.jsterm.autocompletePopup;
+
+ let input = popup._document.activeElement;
+ function getActiveDescendant() {
+ return input.ownerDocument.getElementById(
+ input.getAttribute("aria-activedescendant"));
+ }
+
+ ok(!popup.isOpen, "popup is not open");
+ ok(!input.hasAttribute("aria-activedescendant"), "no aria-activedescendant");
+
+ popup._panel.addEventListener("popupshown", function() {
+ popup._panel.removeEventListener("popupshown", arguments.callee, false);
+
+ ok(popup.isOpen, "popup is open");
+
+ is(popup.itemCount, 0, "no items");
+ ok(!input.hasAttribute("aria-activedescendant"),
+ "no aria-activedescendant");
+
+ popup.setItems(items);
+
+ is(popup.itemCount, items.length, "items added");
+
+ let sameItems = popup.getItems();
+ is(sameItems.every(function(aItem, aIndex) {
+ return aItem === items[aIndex];
+ }), true, "getItems returns back the same items");
+
+ is(popup.selectedIndex, 2,
+ "Index of the first item from bottom is selected.");
+ is(popup.selectedItem, items[2], "First item from bottom is selected");
+ ok(getActiveDescendant().selected, "aria-activedescendant is correct");
+
+ popup.selectedIndex = 1;
+
+ is(popup.selectedIndex, 1, "index 1 is selected");
+ is(popup.selectedItem, items[1], "item1 is selected");
+ ok(getActiveDescendant().selected, "aria-activedescendant is correct");
+
+ popup.selectedItem = items[2];
+
+ is(popup.selectedIndex, 2, "index 2 is selected");
+ is(popup.selectedItem, items[2], "item2 is selected");
+ ok(getActiveDescendant().selected, "aria-activedescendant is correct");
+
+ is(popup.selectPreviousItem(), items[1], "selectPreviousItem() works");
+
+ is(popup.selectedIndex, 1, "index 1 is selected");
+ is(popup.selectedItem, items[1], "item1 is selected");
+ ok(getActiveDescendant().selected, "aria-activedescendant is correct");
+
+ is(popup.selectNextItem(), items[2], "selectPreviousItem() works");
+
+ is(popup.selectedIndex, 2, "index 2 is selected");
+ is(popup.selectedItem, items[2], "item2 is selected");
+ ok(getActiveDescendant().selected, "aria-activedescendant is correct");
+
+ ok(popup.selectNextItem(), "selectPreviousItem() works");
+
+ is(popup.selectedIndex, 0, "index 0 is selected");
+ is(popup.selectedItem, items[0], "item0 is selected");
+ ok(getActiveDescendant().selected, "aria-activedescendant is correct");
+
+ items.push({label: "label3", value: "value3"});
+ popup.appendItem(items[3]);
+
+ is(popup.itemCount, items.length, "item3 appended");
+
+ popup.selectedIndex = 3;
+ is(popup.selectedItem, items[3], "item3 is selected");
+ ok(getActiveDescendant().selected, "aria-activedescendant is correct");
+
+ popup.removeItem(items[2]);
+
+ is(popup.selectedIndex, 2, "index2 is selected");
+ is(popup.selectedItem, items[3], "item3 is still selected");
+ ok(getActiveDescendant().selected, "aria-activedescendant is correct");
+ is(popup.itemCount, items.length - 1, "item2 removed");
+
+ popup.clearItems();
+ is(popup.itemCount, 0, "items cleared");
+ ok(!input.hasAttribute("aria-activedescendant"),
+ "no aria-activedescendant");
+
+ popup.hidePopup();
+ deferred.resolve();
+ }, false);
+
+ popup.openPopup();
+
+ return deferred.promise;
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_586388_select_all.js b/browser/devtools/webconsole/test/browser_webconsole_bug_586388_select_all.js
new file mode 100644
index 000000000..5512201cf
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_586388_select_all.js
@@ -0,0 +1,91 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Patrick Walton <pcwalton@mozilla.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const TEST_URI = "http://example.com/";
+
+"use strict";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+ yield testSelectionWhenMovingBetweenBoxes(hud);
+ performTestsAfterOutput(hud);
+})
+
+let testSelectionWhenMovingBetweenBoxes = Task.async(function *(aHud) {
+ let hud = aHud;
+ let jsterm = hud.jsterm;
+
+ // Fill the console with some output.
+ jsterm.clearOutput();
+ yield jsterm.execute("1 + 2");
+ yield jsterm.execute("3 + 4");
+ yield jsterm.execute("5 + 6");
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "3",
+ category: CATEGORY_OUTPUT,
+ },
+ {
+ text: "7",
+ category: CATEGORY_OUTPUT,
+ },
+ {
+ text: "11",
+ category: CATEGORY_OUTPUT,
+ }],
+ });
+});
+
+function performTestsAfterOutput(aHud) {
+ let hud = aHud;
+ let outputNode = hud.outputNode;
+
+ ok(outputNode.childNodes.length >= 3, "the output node has children after " +
+ "executing some JavaScript");
+
+ // Test that the global Firefox "Select All" functionality (e.g. Edit >
+ // Select All) works properly in the Web Console.
+ let commandController = hud.ui._commandController;
+ ok(commandController != null, "the window has a command controller object");
+
+ commandController.selectAll();
+
+ let selectedCount = hud.ui.output.getSelectedMessages().length;
+ is(selectedCount, outputNode.childNodes.length,
+ "all console messages are selected after performing a regular browser " +
+ "select-all operation");
+
+ hud.iframeWindow.getSelection().removeAllRanges();
+
+ // Test the context menu "Select All" (which has a different code path) works
+ // properly as well.
+ let contextMenuId = outputNode.parentNode.getAttribute("context");
+ let contextMenu = hud.ui.document.getElementById(contextMenuId);
+ ok(contextMenu != null, "the output node has a context menu");
+
+ let selectAllItem = contextMenu.querySelector("*[command='cmd_selectAll']");
+ ok(selectAllItem != null,
+ "the context menu on the output node has a \"Select All\" item");
+
+ outputNode.focus();
+
+ selectAllItem.doCommand();
+
+ selectedCount = hud.ui.output.getSelectedMessages().length;
+ is(selectedCount, outputNode.childNodes.length,
+ "all console messages are selected after performing a select-all " +
+ "operation from the context menu");
+
+ hud.iframeWindow.getSelection().removeAllRanges();
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_587617_output_copy.js b/browser/devtools/webconsole/test/browser_webconsole_bug_587617_output_copy.js
new file mode 100644
index 000000000..78a4221c4
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_587617_output_copy.js
@@ -0,0 +1,98 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ * Patrick Walton <pcwalton@mozilla.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let HUD, outputNode;
+
+"use strict";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+ yield consoleOpened(hud);
+ yield testContextMenuCopy();
+
+ HUD = outputNode = null;
+});
+
+function consoleOpened(aHud) {
+ HUD = aHud;
+
+ let deferred = promise.defer();
+
+ // See bugs 574036, 586386 and 587617.
+ outputNode = HUD.outputNode;
+
+ HUD.jsterm.clearOutput();
+
+ let controller = top.document.commandDispatcher.
+ getControllerForCommand("cmd_copy");
+ is(controller.isCommandEnabled("cmd_copy"), false, "cmd_copy is disabled");
+
+ content.console.log("Hello world! bug587617");
+
+ waitForMessages({
+ webconsole: HUD,
+ messages: [{
+ text: "bug587617",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ }).then(([result]) => {
+ let msg = [...result.matched][0];
+ HUD.ui.output.selectMessage(msg);
+
+ outputNode.focus();
+
+ goUpdateCommand("cmd_copy");
+ controller = top.document.commandDispatcher.getControllerForCommand("cmd_copy");
+ is(controller.isCommandEnabled("cmd_copy"), true, "cmd_copy is enabled");
+
+ // Remove new lines since getSelection() includes one between message and line
+ // number, but the clipboard doesn't (see bug 1119503)
+ let selection = (HUD.iframeWindow.getSelection() + "").replace(/\r?\n|\r/g, " ");
+ isnot(selection.indexOf("bug587617"), -1,
+ "selection text includes 'bug587617'");
+
+ waitForClipboard((str) => { return selection.trim() == str.trim(); },
+ () => { goDoCommand("cmd_copy") },
+ deferred.resolve, deferred.resolve);
+ });
+ return deferred.promise;
+}
+
+// Test that the context menu "Copy" (which has a different code path) works
+// properly as well.
+function testContextMenuCopy() {
+ let deferred = promise.defer();
+
+ let contextMenuId = outputNode.parentNode.getAttribute("context");
+ let contextMenu = HUD.ui.document.getElementById(contextMenuId);
+ ok(contextMenu, "the output node has a context menu");
+
+ let copyItem = contextMenu.querySelector("*[command='cmd_copy']");
+ ok(copyItem, "the context menu on the output node has a \"Copy\" item");
+
+ // Remove new lines since getSelection() includes one between message and line
+ // number, but the clipboard doesn't (see bug 1119503)
+ let selection = (HUD.iframeWindow.getSelection() + "").replace(/\r?\n|\r/g, " ");
+
+ copyItem.doCommand();
+
+ waitForClipboard((str) => { return selection.trim() == str.trim(); },
+ () => { goDoCommand("cmd_copy") },
+ deferred.resolve, deferred.resolve);
+ HUD = outputNode = null;
+
+ return deferred.promise;
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_588342_document_focus.js b/browser/devtools/webconsole/test/browser_webconsole_bug_588342_document_focus.js
new file mode 100644
index 000000000..6e7fcc1f6
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_588342_document_focus.js
@@ -0,0 +1,39 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 588342";
+
+let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+
+"use strict";
+
+let test = asyncTest(function* () {
+ let { browser } = yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+ yield consoleOpened(hud);
+
+ is(fm.focusedWindow, browser.contentWindow,
+ "content document has focus");
+
+ fm = null;
+});
+
+function consoleOpened(hud) {
+ let deferred = promise.defer();
+ waitForFocus(function() {
+ is(hud.jsterm.inputNode.getAttribute("focused"), "true",
+ "jsterm input is focused on web console open");
+ isnot(fm.focusedWindow, content, "content document has no focus");
+ closeConsole(null).then(deferred.resolve);
+ }, hud.iframeWindow);
+
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_588730_text_node_insertion.js b/browser/devtools/webconsole/test/browser_webconsole_bug_588730_text_node_insertion.js
new file mode 100644
index 000000000..ef1c3e5a2
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_588730_text_node_insertion.js
@@ -0,0 +1,53 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that adding text to one of the output labels doesn't cause errors.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+"use strict";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ yield testTextNodeInsertion(hud);
+});
+
+// Test for bug 588730: Adding a text node to an existing label element causes
+// warnings
+function testTextNodeInsertion(hud) {
+ let deferred = promise.defer();
+ let outputNode = hud.outputNode;
+
+ let label = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "label");
+ outputNode.appendChild(label);
+
+ let error = false;
+ let listener = {
+ observe: function(aMessage) {
+ let messageText = aMessage.message;
+ if (messageText.indexOf("JavaScript Warning") !== -1) {
+ error = true;
+ }
+ }
+ };
+
+ Services.console.registerListener(listener);
+
+ // This shouldn't fail.
+ label.appendChild(document.createTextNode("foo"));
+
+ executeSoon(function() {
+ Services.console.unregisterListener(listener);
+ ok(!error, "no error when adding text nodes as children of labels");
+
+ return deferred.resolve();
+ });
+ return deferred.promise;
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_588967_input_expansion.js b/browser/devtools/webconsole/test/browser_webconsole_bug_588967_input_expansion.js
new file mode 100644
index 000000000..9b9fb3168
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_588967_input_expansion.js
@@ -0,0 +1,45 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+"use strict";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ testInputExpansion(hud);
+});
+
+
+function testInputExpansion(hud) {
+ let input = hud.jsterm.inputNode;
+
+ input.focus();
+
+ is(input.getAttribute("multiline"), "true", "multiline is enabled");
+
+ let ordinaryHeight = input.clientHeight;
+
+ // Tests if the inputNode expands.
+ input.value = "hello\nworld\n";
+ let length = input.value.length;
+ input.selectionEnd = length;
+ input.selectionStart = length;
+ // Performs an "d". This will trigger/test for the input event that should
+ // change the height of the inputNode.
+ EventUtils.synthesizeKey("d", {});
+ ok(input.clientHeight > ordinaryHeight, "the input expanded");
+
+ // Test if the inputNode shrinks again.
+ input.value = "";
+ EventUtils.synthesizeKey("d", {});
+ is(input.clientHeight, ordinaryHeight, "the input's height is normal again");
+
+ input = length = null;
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_589162_css_filter.js b/browser/devtools/webconsole/test/browser_webconsole_bug_589162_css_filter.js
new file mode 100644
index 000000000..72fd295eb
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_589162_css_filter.js
@@ -0,0 +1,49 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ * Patrick Walton <pcwalton@mozilla.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict"
+
+const TEST_URI = "data:text/html;charset=utf-8,<div style='font-size:3em;" +
+ "foobarCssParser:baz'>test CSS parser filter</div>";
+
+/**
+ * Unit test for bug 589162:
+ * CSS filtering on the console does not work
+ */
+function test() {
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ let {tab} = yield loadTab(TEST_URI);
+ let hud = yield openConsole(tab);
+
+ // CSS warnings are disabled by default.
+ hud.setFilterState("cssparser", true);
+ hud.jsterm.clearOutput();
+
+ content.location.reload();
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "foobarCssParser",
+ category: CATEGORY_CSS,
+ severity: SEVERITY_WARNING,
+ }],
+ });
+
+ hud.setFilterState("cssparser", false);
+
+ let msg = "the unknown CSS property warning is not displayed, " +
+ "after filtering";
+ testLogEntry(hud.outputNode, "foobarCssParser", msg, true, true);
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_592442_closing_brackets.js b/browser/devtools/webconsole/test/browser_webconsole_bug_592442_closing_brackets.js
new file mode 100644
index 000000000..c29e61b26
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_592442_closing_brackets.js
@@ -0,0 +1,37 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Julian Viereck <jviereck@mozilla.com>
+ * Patrick Walton <pcwalton@mozilla.com>
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Tests that, when the user types an extraneous closing bracket, no error
+// appears.
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf-8,test for bug 592442";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ hud.jsterm.clearOutput();
+ let jsterm = hud.jsterm;
+
+ jsterm.setInputValue("document.getElementById)");
+
+ let error = false;
+ try {
+ jsterm.complete(jsterm.COMPLETE_HINT_ONLY);
+ }
+ catch (ex) {
+ error = true;
+ }
+
+ ok(!error, "no error was thrown when an extraneous bracket was inserted");
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_593003_iframe_wrong_hud.js b/browser/devtools/webconsole/test/browser_webconsole_bug_593003_iframe_wrong_hud.js
new file mode 100644
index 000000000..ac526b502
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_593003_iframe_wrong_hud.js
@@ -0,0 +1,65 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-593003-iframe-wrong-hud.html";
+
+const TEST_IFRAME_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-593003-iframe-wrong-hud-iframe.html";
+
+const TEST_DUMMY_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let tab1, tab2;
+
+function test() {
+ loadTab(TEST_URI).then(({tab}) => {
+ tab1 = tab;
+
+ content.console.log("FOO");
+ openConsole().then(() => {
+ tab2 = gBrowser.addTab(TEST_DUMMY_URI);
+ gBrowser.selectedTab = tab2;
+ gBrowser.selectedBrowser.addEventListener("load", tab2Loaded, true);
+ });
+ });
+}
+
+function tab2Loaded(aEvent) {
+ tab2.linkedBrowser.removeEventListener(aEvent.type, tab2Loaded, true);
+
+ openConsole(gBrowser.selectedTab).then(() => {
+ tab1.linkedBrowser.addEventListener("load", tab1Reloaded, true);
+ tab1.linkedBrowser.contentWindow.location.reload();
+ });
+}
+
+function tab1Reloaded(aEvent) {
+ tab1.linkedBrowser.removeEventListener(aEvent.type, tab1Reloaded, true);
+
+ let hud1 = HUDService.getHudByWindow(tab1.linkedBrowser.contentWindow);
+ let outputNode1 = hud1.outputNode;
+
+ waitForMessages({
+ webconsole: hud1,
+ messages: [{
+ text: TEST_IFRAME_URI,
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ }],
+ }).then(() => {
+ let hud2 = HUDService.getHudByWindow(tab2.linkedBrowser.contentWindow);
+ let outputNode2 = hud2.outputNode;
+
+ isnot(outputNode1, outputNode2,
+ "the two HUD outputNodes must be different");
+
+ let msg = "Didn't find the iframe network request in tab2";
+ testLogEntry(outputNode2, TEST_IFRAME_URI, msg, true, true);
+
+ closeConsole(tab2).then(() => {
+ gBrowser.removeTab(tab2);
+ tab1 = tab2 = null;
+ executeSoon(finishTest);
+ });
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_594477_clickable_output.js b/browser/devtools/webconsole/test/browser_webconsole_bug_594477_clickable_output.js
new file mode 100644
index 000000000..c6a983d6b
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_594477_clickable_output.js
@@ -0,0 +1,131 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+let HUD;
+let outputItem;
+let outputNode;
+
+"use strict";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ HUD = yield openConsole();
+ outputNode = HUD.outputNode;
+
+ // reload the tab
+ BrowserReload();
+ yield loadBrowser(gBrowser.selectedBrowser);
+
+ let event = yield clickEvents();
+ yield testClickAgain(event);
+ yield networkPanelHidden();
+
+ HUD = outputItem = outputNode = null;
+});
+
+function clickEvents() {
+ let deferred = promise.defer();
+
+ waitForMessages({
+ webconsole: HUD,
+ messages: [{
+ text: "test-console.html",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ }],
+ }).then(([result]) => {
+ let msg = [...result.matched][0];
+ outputItem = msg.querySelector(".message-body .url");
+ ok(outputItem, "found a network message");
+ document.addEventListener("popupshown", function onPanelShown(event) {
+ document.removeEventListener("popupshown", onPanelShown, false);
+ deferred.resolve(event);
+ }, false);
+
+ // Send the mousedown and click events such that the network panel opens.
+ EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
+ EventUtils.sendMouseEvent({type: "click"}, outputItem);
+ });
+
+ return deferred.promise;
+}
+
+function testClickAgain(event) {
+ info("testClickAgain");
+
+ let deferred = promise.defer();
+
+ document.addEventListener("popupshown", networkPanelShowFailure, false);
+
+ // The network panel should not open for the second time.
+ EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
+ EventUtils.sendMouseEvent({type: "click"}, outputItem);
+
+ executeSoon(function() {
+ document.addEventListener("popuphidden", function onHidden() {
+ document.removeEventListener("popuphidden", onHidden, false);
+ deferred.resolve();
+ }, false);
+ event.target.hidePopup();
+ });
+
+ return deferred.promise;
+}
+
+function networkPanelShowFailure() {
+ ok(false, "the network panel should not show");
+}
+
+function networkPanelHidden() {
+ let deferred = promise.defer();
+
+ info("networkPanelHidden");
+
+ // The network panel should not show because this is a mouse event that starts
+ // in a position and ends in another.
+ EventUtils.sendMouseEvent({type: "mousedown", clientX: 3, clientY: 4},
+ outputItem);
+ EventUtils.sendMouseEvent({type: "click", clientX: 5, clientY: 6},
+ outputItem);
+
+ // The network panel should not show because this is a middle-click.
+ EventUtils.sendMouseEvent({type: "mousedown", button: 1},
+ outputItem);
+ EventUtils.sendMouseEvent({type: "click", button: 1},
+ outputItem);
+
+ // The network panel should not show because this is a right-click.
+ EventUtils.sendMouseEvent({type: "mousedown", button: 2},
+ outputItem);
+ EventUtils.sendMouseEvent({type: "click", button: 2},
+ outputItem);
+
+ executeSoon(function() {
+ document.removeEventListener("popupshown", networkPanelShowFailure, false);
+
+ // Done with the network output. Now test the jsterm output and the property
+ // panel.
+ HUD.jsterm.execute("document").then((msg) => {
+ info("jsterm execute 'document' callback");
+
+ HUD.jsterm.once("variablesview-open", deferred.resolve);
+ let outputItem = msg.querySelector(".message-body a");
+ ok(outputItem, "jsterm output message found");
+
+ // Send the mousedown and click events such that the property panel opens.
+ EventUtils.sendMouseEvent({type: "mousedown"}, outputItem);
+ EventUtils.sendMouseEvent({type: "click"}, outputItem);
+ });
+ });
+
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_594497_history_arrow_keys.js b/browser/devtools/webconsole/test/browser_webconsole_bug_594497_history_arrow_keys.js
new file mode 100644
index 000000000..38210ebfb
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_594497_history_arrow_keys.js
@@ -0,0 +1,156 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let inputNode, values;
+
+let TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 594497 and bug 619598";
+"use strict";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ setup(hud);
+ performTests();
+
+ inputNode = values = null;
+});
+
+function setup(HUD) {
+ inputNode = HUD.jsterm.inputNode;
+
+ inputNode.focus();
+
+ ok(!inputNode.value, "inputNode.value is empty");
+
+ values = ["document", "window", "document.body"];
+ values.push(values.join(";\n"), "document.location");
+
+ // Execute each of the values;
+ for (let i = 0; i < values.length; i++) {
+ HUD.jsterm.setInputValue(values[i]);
+ HUD.jsterm.execute();
+ }
+}
+
+function performTests() {
+ EventUtils.synthesizeKey("VK_UP", {});
+
+ is(inputNode.value, values[4],
+ "VK_UP: inputNode.value #4 is correct");
+
+ ok(inputNode.selectionStart == values[4].length &&
+ inputNode.selectionStart == inputNode.selectionEnd,
+ "caret location is correct");
+
+ EventUtils.synthesizeKey("VK_UP", {});
+
+ is(inputNode.value, values[3],
+ "VK_UP: inputNode.value #3 is correct");
+
+ ok(inputNode.selectionStart == values[3].length &&
+ inputNode.selectionStart == inputNode.selectionEnd,
+ "caret location is correct");
+
+ inputNode.setSelectionRange(values[3].length - 2, values[3].length - 2);
+
+ EventUtils.synthesizeKey("VK_UP", {});
+ EventUtils.synthesizeKey("VK_UP", {});
+
+ is(inputNode.value, values[3],
+ "VK_UP two times: inputNode.value #3 is correct");
+
+ ok(inputNode.selectionStart == inputNode.value.indexOf("\n") &&
+ inputNode.selectionStart == inputNode.selectionEnd,
+ "caret location is correct");
+
+ EventUtils.synthesizeKey("VK_UP", {});
+
+ is(inputNode.value, values[3],
+ "VK_UP again: inputNode.value #3 is correct");
+
+ ok(inputNode.selectionStart == 0 &&
+ inputNode.selectionStart == inputNode.selectionEnd,
+ "caret location is correct");
+
+ EventUtils.synthesizeKey("VK_UP", {});
+
+ is(inputNode.value, values[2],
+ "VK_UP: inputNode.value #2 is correct");
+
+ EventUtils.synthesizeKey("VK_UP", {});
+
+ is(inputNode.value, values[1],
+ "VK_UP: inputNode.value #1 is correct");
+
+ EventUtils.synthesizeKey("VK_UP", {});
+
+ is(inputNode.value, values[0],
+ "VK_UP: inputNode.value #0 is correct");
+
+ ok(inputNode.selectionStart == values[0].length &&
+ inputNode.selectionStart == inputNode.selectionEnd,
+ "caret location is correct");
+
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ is(inputNode.value, values[1],
+ "VK_DOWN: inputNode.value #1 is correct");
+
+ ok(inputNode.selectionStart == values[1].length &&
+ inputNode.selectionStart == inputNode.selectionEnd,
+ "caret location is correct");
+
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ is(inputNode.value, values[2],
+ "VK_DOWN: inputNode.value #2 is correct");
+
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ is(inputNode.value, values[3],
+ "VK_DOWN: inputNode.value #3 is correct");
+
+ ok(inputNode.selectionStart == values[3].length &&
+ inputNode.selectionStart == inputNode.selectionEnd,
+ "caret location is correct");
+
+ inputNode.setSelectionRange(2, 2);
+
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ is(inputNode.value, values[3],
+ "VK_DOWN two times: inputNode.value #3 is correct");
+
+ ok(inputNode.selectionStart > inputNode.value.lastIndexOf("\n") &&
+ inputNode.selectionStart == inputNode.selectionEnd,
+ "caret location is correct");
+
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ is(inputNode.value, values[3],
+ "VK_DOWN again: inputNode.value #3 is correct");
+
+ ok(inputNode.selectionStart == values[3].length &&
+ inputNode.selectionStart == inputNode.selectionEnd,
+ "caret location is correct");
+
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ is(inputNode.value, values[4],
+ "VK_DOWN: inputNode.value #4 is correct");
+
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ ok(!inputNode.value,
+ "VK_DOWN: inputNode.value is empty");
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_595223_file_uri.js b/browser/devtools/webconsole/test/browser_webconsole_bug_595223_file_uri.js
new file mode 100644
index 000000000..fe8ff7b2c
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_595223_file_uri.js
@@ -0,0 +1,64 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+"use strict";
+
+const PREF = "devtools.webconsole.persistlog";
+const TEST_FILE = "test-network.html";
+const TEST_URI = "data:text/html;charset=utf8,<p>test file URI";
+
+let hud;
+
+let test = asyncTest(function* () {
+ Services.prefs.setBoolPref(PREF, true);
+
+ let jar = getJar(getRootDirectory(gTestPath));
+ let dir = jar ?
+ extractJarToTmp(jar) :
+ getChromeDir(getResolvedURI(gTestPath));
+
+ dir.append(TEST_FILE);
+ let uri = Services.io.newFileURI(dir);
+
+ let { browser } = yield loadTab(TEST_URI);
+
+ hud = yield openConsole();
+ hud.jsterm.clearOutput();
+
+ let loaded = loadBrowser(browser);
+ content.location = uri.spec;
+ yield loaded;
+
+ yield testMessages();
+
+ Services.prefs.clearUserPref(PREF);
+ hud = null;
+});
+
+function testMessages() {
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "running network console logging tests",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "test-network.html",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "test-image.png",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "testscript.js",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ }],
+ })
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_595350_multiple_windows_and_tabs.js b/browser/devtools/webconsole/test/browser_webconsole_bug_595350_multiple_windows_and_tabs.js
new file mode 100644
index 000000000..cefb3e377
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_595350_multiple_windows_and_tabs.js
@@ -0,0 +1,101 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Patrick Walton <pcwalton@mozilla.com>
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Tests that the Web Console doesn't leak when multiple tabs and windows are
+// opened and then closed.
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 595350";
+
+let win1 = window, win2;
+let openTabs = [];
+let loadedTabCount = 0;
+
+function test() {
+ requestLongerTimeout(3);
+
+ // Add two tabs in the main window.
+ addTabs(win1);
+
+ // Open a new window.
+ win2 = OpenBrowserWindow();
+ win2.addEventListener("load", onWindowLoad, true);
+}
+
+function onWindowLoad(aEvent) {
+ win2.removeEventListener(aEvent.type, onWindowLoad, true);
+
+ // Add two tabs in the new window.
+ addTabs(win2);
+}
+
+function addTabs(aWindow) {
+ for (let i = 0; i < 2; i++) {
+ let tab = aWindow.gBrowser.addTab(TEST_URI);
+ openTabs.push(tab);
+
+ tab.linkedBrowser.addEventListener("load", function onLoad(aEvent) {
+ tab.linkedBrowser.removeEventListener(aEvent.type, onLoad, true);
+
+ loadedTabCount++;
+ info("tabs loaded: " + loadedTabCount);
+ if (loadedTabCount >= 4) {
+ executeSoon(openConsoles);
+ }
+ }, true);
+ }
+}
+
+function openConsoles() {
+ // open the Web Console for each of the four tabs and log a message.
+ let consolesOpen = 0;
+ for (let i = 0; i < openTabs.length; i++) {
+ let tab = openTabs[i];
+ openConsole(tab).then(function(index, hud) {
+ ok(hud, "HUD is open for tab " + index);
+ let window = hud.target.tab.linkedBrowser.contentWindow;
+ window.console.log("message for tab " + index);
+ consolesOpen++;
+ if (consolesOpen == 4) {
+ // Use executeSoon() to allow the promise to resolve.
+ executeSoon(closeConsoles);
+ }
+ }.bind(null, i));
+ }
+}
+
+function closeConsoles() {
+ let consolesClosed = 0;
+
+ function onWebConsoleClose(aSubject, aTopic) {
+ if (aTopic == "web-console-destroyed") {
+ consolesClosed++;
+ info("consoles destroyed: " + consolesClosed);
+ if (consolesClosed == 4) {
+ // Use executeSoon() to allow all the observers to execute.
+ executeSoon(finishTest);
+ }
+ }
+ }
+
+ Services.obs.addObserver(onWebConsoleClose, "web-console-destroyed", false);
+
+ registerCleanupFunction(() => {
+ Services.obs.removeObserver(onWebConsoleClose, "web-console-destroyed");
+ });
+
+ win2.close();
+
+ win1.gBrowser.removeTab(openTabs[0]);
+ win1.gBrowser.removeTab(openTabs[1]);
+
+ openTabs = win1 = win2 = null;
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_595934_message_categories.js b/browser/devtools/webconsole/test/browser_webconsole_bug_595934_message_categories.js
new file mode 100644
index 000000000..bfafa4423
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_595934_message_categories.js
@@ -0,0 +1,203 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 595934 - message categories coverage.";
+const TESTS_PATH = "http://example.com/browser/browser/devtools/webconsole/test/";
+const TESTS = [
+ { // #0
+ file: "test-bug-595934-css-loader.html",
+ category: "CSS Loader",
+ matchString: "text/css",
+ },
+ { // #1
+ file: "test-bug-595934-imagemap.html",
+ category: "Layout: ImageMap",
+ matchString: "shape=\"rect\"",
+ },
+ { // #2
+ file: "test-bug-595934-html.html",
+ category: "HTML",
+ matchString: "multipart/form-data",
+ onload: function() {
+ let form = content.document.querySelector("form");
+ form.submit();
+ },
+ },
+ { // #3
+ file: "test-bug-595934-workers.html",
+ category: "Web Worker",
+ matchString: "fooBarWorker",
+ expectError: true,
+ },
+ { // #4
+ file: "test-bug-595934-malformedxml.xhtml",
+ category: "malformed-xml",
+ matchString: "no element found",
+ },
+ { // #5
+ file: "test-bug-595934-svg.xhtml",
+ category: "SVG",
+ matchString: "fooBarSVG",
+ },
+ { // #6
+ file: "test-bug-595934-css-parser.html",
+ category: "CSS Parser",
+ matchString: "foobarCssParser",
+ },
+ { // #7
+ file: "test-bug-595934-malformedxml-external.html",
+ category: "malformed-xml",
+ matchString: "</html>",
+ },
+ { // #8
+ file: "test-bug-595934-empty-getelementbyid.html",
+ category: "DOM",
+ matchString: "getElementById",
+ },
+ { // #9
+ file: "test-bug-595934-canvas-css.html",
+ category: "CSS Parser",
+ matchString: "foobarCanvasCssParser",
+ },
+ { // #10
+ file: "test-bug-595934-image.html",
+ category: "Image",
+ matchString: "corrupt",
+ },
+];
+
+let pos = -1;
+
+let foundCategory = false;
+let foundText = false;
+let pageLoaded = false;
+let pageError = false;
+let output = null;
+let jsterm = null;
+let hud = null;
+let testEnded = false;
+
+let TestObserver = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+
+ observe: function test_observe(aSubject)
+ {
+ if (testEnded || !(aSubject instanceof Ci.nsIScriptError)) {
+ return;
+ }
+
+ var expectedCategory = TESTS[pos].category;
+
+ info("test #" + pos + " console observer got " + aSubject.category +
+ ", is expecting " + expectedCategory);
+
+ if (aSubject.category == expectedCategory) {
+ foundCategory = true;
+ startNextTest();
+ }
+ else {
+ info("unexpected message was: " + aSubject.sourceName + ":" +
+ aSubject.lineNumber + "; " + aSubject.errorMessage);
+ }
+ }
+};
+
+function consoleOpened(aHud) {
+ hud = aHud;
+ output = hud.outputNode;
+ jsterm = hud.jsterm;
+
+ Services.console.registerListener(TestObserver);
+
+ registerCleanupFunction(testEnd);
+
+ testNext();
+}
+
+function testNext() {
+ jsterm.clearOutput();
+ foundCategory = false;
+ foundText = false;
+ pageLoaded = false;
+ pageError = false;
+
+ pos++;
+ info("testNext: #" + pos);
+ if (pos < TESTS.length) {
+ let test = TESTS[pos];
+
+ waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "message for test #" + pos + ": '" + test.matchString +"'",
+ text: test.matchString,
+ }],
+ }).then(() => {
+ foundText = true;
+ startNextTest();
+ });
+
+ let testLocation = TESTS_PATH + test.file;
+ gBrowser.selectedBrowser.addEventListener("load", function onLoad(aEvent) {
+ if (content.location.href != testLocation) {
+ return;
+ }
+ gBrowser.selectedBrowser.removeEventListener(aEvent.type, onLoad, true);
+
+ pageLoaded = true;
+ test.onload && test.onload(aEvent);
+
+ if (test.expectError) {
+ content.addEventListener("error", function _onError() {
+ content.removeEventListener("error", _onError);
+ pageError = true;
+ startNextTest();
+ });
+ expectUncaughtException();
+ }
+ else {
+ pageError = true;
+ }
+
+ startNextTest();
+ }, true);
+
+ content.location = testLocation;
+ }
+ else {
+ testEnded = true;
+ finishTest();
+ }
+}
+
+function testEnd() {
+ if (!testEnded) {
+ info("foundCategory " + foundCategory + " foundText " + foundText +
+ " pageLoaded " + pageLoaded + " pageError " + pageError);
+ }
+
+ Services.console.unregisterListener(TestObserver);
+ hud = TestObserver = output = jsterm = null;
+}
+
+function startNextTest() {
+ if (!testEnded && foundCategory && foundText && pageLoaded && pageError) {
+ testNext();
+ }
+}
+
+function test() {
+ requestLongerTimeout(2);
+
+ loadTab(TEST_URI).then(() => {
+ openConsole().then(consoleOpened);
+ });
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_597103_deactivateHUDForContext_unfocused_window.js b/browser/devtools/webconsole/test/browser_webconsole_bug_597103_deactivateHUDForContext_unfocused_window.js
new file mode 100644
index 000000000..6bc20ba1c
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_597103_deactivateHUDForContext_unfocused_window.js
@@ -0,0 +1,104 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let tab1, tab2, win1, win2;
+let noErrors = true;
+
+function tab1Loaded() {
+ win2 = OpenBrowserWindow();
+ whenDelayedStartupFinished(win2, win2Loaded);
+}
+
+function win2Loaded() {
+ tab2 = win2.gBrowser.addTab(TEST_URI);
+ win2.gBrowser.selectedTab = tab2;
+ tab2.linkedBrowser.addEventListener("load", tab2Loaded, true);
+}
+
+function tab2Loaded(aEvent) {
+ tab2.linkedBrowser.removeEventListener(aEvent.type, tab2Loaded, true);
+
+ let consolesOpened = 0;
+ function onWebConsoleOpen() {
+ consolesOpened++;
+ if (consolesOpened == 2) {
+ executeSoon(closeConsoles);
+ }
+ }
+
+ function openConsoles() {
+ try {
+ let target1 = TargetFactory.forTab(tab1);
+ gDevTools.showToolbox(target1, "webconsole").then(onWebConsoleOpen);
+ }
+ catch (ex) {
+ ok(false, "gDevTools.showToolbox(target1) exception: " + ex);
+ noErrors = false;
+ }
+
+ try {
+ let target2 = TargetFactory.forTab(tab2);
+ gDevTools.showToolbox(target2, "webconsole").then(onWebConsoleOpen);
+ }
+ catch (ex) {
+ ok(false, "gDevTools.showToolbox(target2) exception: " + ex);
+ noErrors = false;
+ }
+ }
+
+ function closeConsoles() {
+ try {
+ let target1 = TargetFactory.forTab(tab1);
+ gDevTools.closeToolbox(target1).then(function() {
+ try {
+ let target2 = TargetFactory.forTab(tab2);
+ gDevTools.closeToolbox(target2).then(testEnd);
+ }
+ catch (ex) {
+ ok(false, "gDevTools.closeToolbox(target2) exception: " + ex);
+ noErrors = false;
+ }
+ });
+ }
+ catch (ex) {
+ ok(false, "gDevTools.closeToolbox(target1) exception: " + ex);
+ noErrors = false;
+ }
+ }
+
+ function testEnd() {
+ ok(noErrors, "there were no errors");
+
+ win1.gBrowser.removeTab(tab1);
+
+ Array.forEach(win2.gBrowser.tabs, function(aTab) {
+ win2.gBrowser.removeTab(aTab);
+ });
+
+ executeSoon(function() {
+ win2.close();
+ tab1 = tab2 = win1 = win2 = null;
+ finishTest();
+ });
+ }
+
+ waitForFocus(openConsoles, tab2.linkedBrowser.contentWindow);
+}
+
+function test() {
+ loadTab(TEST_URI).then(() => {
+ tab1 = gBrowser.selectedTab;
+ win1 = window;
+ tab1Loaded();
+ });
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_597136_external_script_errors.js b/browser/devtools/webconsole/test/browser_webconsole_bug_597136_external_script_errors.js
new file mode 100644
index 000000000..fa8905af0
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_597136_external_script_errors.js
@@ -0,0 +1,36 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Patrick Walton <pcwalton@mozilla.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/" +
+ "webconsole/test/test-bug-597136-external-script-" +
+ "errors.html";
+
+function test() {
+ Task.spawn(function* () {
+ const {tab} = yield loadTab(TEST_URI);
+ const hud = yield openConsole(tab);
+
+ let button = content.document.querySelector("button");
+
+ expectUncaughtException();
+ EventUtils.sendMouseEvent({ type: "click" }, button, content);
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "bogus is not defined",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ }],
+ });
+ }).then(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_597136_network_requests_from_chrome.js b/browser/devtools/webconsole/test/browser_webconsole_bug_597136_network_requests_from_chrome.js
new file mode 100644
index 000000000..bf745ac3d
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_597136_network_requests_from_chrome.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that network requests from chrome don't cause the Web Console to
+// throw exceptions.
+
+const TEST_URI = "http://example.com/";
+
+let good = true;
+let listener = {
+ QueryInterface: XPCOMUtils.generateQI([ Ci.nsIObserver ]),
+ observe: function(aSubject, aTopic, aData) {
+ if (aSubject instanceof Ci.nsIScriptError &&
+ aSubject.category === "XPConnect JavaScript" &&
+ aSubject.sourceName.contains("webconsole")) {
+ good = false;
+ }
+ }
+};
+
+let xhr;
+
+function test() {
+ Services.console.registerListener(listener);
+
+ HUDService; // trigger a lazy-load of the HUD Service
+
+ xhr = new XMLHttpRequest();
+ xhr.addEventListener("load", xhrComplete, false);
+ xhr.open("GET", TEST_URI, true);
+ xhr.send(null);
+}
+
+function xhrComplete() {
+ xhr.removeEventListener("load", xhrComplete, false);
+ window.setTimeout(checkForException, 0);
+}
+
+function checkForException() {
+ ok(good, "no exception was thrown when sending a network request from a " +
+ "chrome window");
+
+ Services.console.unregisterListener(listener);
+ listener = xhr = null;
+
+ finishTest();
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_597460_filter_scroll.js b/browser/devtools/webconsole/test/browser_webconsole_bug_597460_filter_scroll.js
new file mode 100644
index 000000000..f8fc821eb
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_597460_filter_scroll.js
@@ -0,0 +1,82 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-network.html";
+const PREF = "devtools.webconsole.persistlog";
+
+let test = asyncTest(function* () {
+ Services.prefs.setBoolPref(PREF, true);
+
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+
+ let results = yield consoleOpened(hud);
+
+ testScroll(results, hud);
+
+ Services.prefs.clearUserPref(PREF);
+});
+
+function consoleOpened(hud) {
+ let deferred = promise.defer();
+
+ for (let i = 0; i < 200; i++) {
+ content.console.log("test message " + i);
+ }
+
+ hud.setFilterState("network", false);
+ hud.setFilterState("networkinfo", false);
+
+ hud.ui.filterBox.value = "test message";
+ hud.ui.adjustVisibilityOnSearchStringChange();
+
+ waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "console messages displayed",
+ text: "test message 199",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ }).then(() => {
+ waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "test-network.html",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ }],
+ }).then(deferred.resolve);
+
+ content.location.reload();
+ });
+
+ return deferred.promise;
+}
+
+function testScroll([result], hud) {
+ let scrollNode = hud.outputNode.parentNode;
+ let msgNode = [...result.matched][0];
+ ok(msgNode.classList.contains("filtered-by-type"),
+ "network message is filtered by type");
+ ok(msgNode.classList.contains("filtered-by-string"),
+ "network message is filtered by string");
+
+ ok(scrollNode.scrollTop > 0, "scroll location is not at the top");
+
+ // Make sure the Web Console output is scrolled as near as possible to the
+ // bottom.
+ let nodeHeight = msgNode.clientHeight;
+ ok(scrollNode.scrollTop >= scrollNode.scrollHeight - scrollNode.clientHeight -
+ nodeHeight * 2, "scroll location is correct");
+
+ hud.setFilterState("network", true);
+ hud.setFilterState("networkinfo", true);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_597756_reopen_closed_tab.js b/browser/devtools/webconsole/test/browser_webconsole_bug_597756_reopen_closed_tab.js
new file mode 100644
index 000000000..ccaedc98e
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_597756_reopen_closed_tab.js
@@ -0,0 +1,62 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-597756-reopen-closed-tab.html";
+
+let HUD;
+
+let test = asyncTest(function* () {
+ expectUncaughtException();
+
+ let { browser } = yield loadTab(TEST_URI);
+ HUD = yield openConsole();
+
+ expectUncaughtException();
+
+ yield reload(browser);
+
+ yield testMessages();
+
+ yield closeConsole();
+
+ // Close and reopen
+ gBrowser.removeCurrentTab();
+
+ expectUncaughtException();
+
+ let tab = yield loadTab(TEST_URI);
+ HUD = yield openConsole();
+
+ expectUncaughtException();
+
+ yield reload(tab.browser);
+
+ yield testMessages();
+
+ HUD = null;
+});
+
+function reload(browser) {
+ let loaded = loadBrowser(browser);
+ content.location.reload();
+ return loaded;
+}
+
+function testMessages() {
+ return waitForMessages({
+ webconsole: HUD,
+ messages: [{
+ name: "error message displayed",
+ text: "fooBug597756_error",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ }],
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_599725_response_headers.js b/browser/devtools/webconsole/test/browser_webconsole_bug_599725_response_headers.js
new file mode 100644
index 000000000..eb3c26cf0
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_599725_response_headers.js
@@ -0,0 +1,87 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const INIT_URI = "data:text/plain;charset=utf8,hello world";
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-599725-response-headers.sjs";
+
+let loads = 0;
+function performTest(aRequest, aConsole)
+{
+ let deferred = promise.defer();
+
+ let headers = null;
+
+ function readHeader(aName)
+ {
+ for (let header of headers) {
+ if (header.name == aName) {
+ return header.value;
+ }
+ }
+ return null;
+ }
+
+ aConsole.webConsoleClient.getResponseHeaders(aRequest.actor,
+ function (aResponse) {
+ headers = aResponse.headers;
+ ok(headers, "we have the response headers for reload");
+
+ let contentType = readHeader("Content-Type");
+ let contentLength = readHeader("Content-Length");
+
+ ok(!contentType, "we do not have the Content-Type header");
+ isnot(contentLength, 60, "Content-Length != 60");
+
+ if (contentType || contentLength == 60) {
+ console.debug("lastFinishedRequest", lastFinishedRequest,
+ "request", lastFinishedRequest.request,
+ "response", lastFinishedRequest.response,
+ "updates", lastFinishedRequest.updates,
+ "response headers", headers);
+ }
+
+ executeSoon(deferred.resolve);
+ });
+
+ HUDService.lastFinishedRequest.callback = null;
+
+ return deferred.promise;
+}
+
+function waitForRequest() {
+ let deferred = promise.defer();
+ HUDService.lastFinishedRequest.callback = (req, console) => {
+ loads++;
+ ok(req, "page load was logged");
+ if (loads != 2) {
+ return;
+ }
+ performTest(req, console).then(deferred.resolve);
+ };
+ return deferred.promise;
+}
+
+let test = asyncTest(function* () {
+ let { browser } = yield loadTab(INIT_URI);
+
+ let hud = yield openConsole();
+
+ let gotLastRequest = waitForRequest();
+
+ let loaded = loadBrowser(browser);
+ content.location = TEST_URI;
+ yield loaded;
+
+ let reloaded = loadBrowser(browser);
+ content.location.reload();
+ yield reloaded;
+
+ yield gotLastRequest;
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_600183_charset.js b/browser/devtools/webconsole/test/browser_webconsole_bug_600183_charset.js
new file mode 100644
index 000000000..bd224375f
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_600183_charset.js
@@ -0,0 +1,66 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const INIT_URI = "data:text/html;charset=utf-8,Web Console - bug 600183 test";
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-600183-charset.html";
+
+function performTest(lastFinishedRequest, aConsole)
+{
+ let deferred = promise.defer();
+
+ ok(lastFinishedRequest, "charset test page was loaded and logged");
+ HUDService.lastFinishedRequest.callback = null;
+
+ executeSoon(() => {
+ aConsole.webConsoleClient.getResponseContent(lastFinishedRequest.actor,
+ (aResponse) => {
+ ok(!aResponse.contentDiscarded, "response body was not discarded");
+
+ let body = aResponse.content.text;
+ ok(body, "we have the response body");
+
+ let chars = "\u7684\u95ee\u5019!"; // 的问候!
+ isnot(body.indexOf("<p>" + chars + "</p>"), -1,
+ "found the chinese simplified string");
+
+ HUDService.lastFinishedRequest.callback = null;
+ executeSoon(deferred.resolve);
+ });
+ });
+
+ return deferred.promise;
+}
+
+function waitForRequest() {
+ let deferred = promise.defer();
+ HUDService.lastFinishedRequest.callback = (req, console) => {
+ performTest(req, console).then(deferred.resolve);
+ };
+ return deferred.promise;
+}
+
+let test = asyncTest(function* () {
+ let { browser } = yield loadTab(INIT_URI);
+
+ let hud = yield openConsole();
+
+ yield hud.ui.setSaveRequestAndResponseBodies(true);
+
+ ok(hud.ui._saveRequestAndResponseBodies,
+ "The saveRequestAndResponseBodies property was successfully set.");
+
+ let gotLastRequest = waitForRequest();
+
+ let loaded = loadBrowser(browser);
+ content.location = TEST_URI;
+ yield loaded;
+
+ yield gotLastRequest;
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_601177_log_levels.js b/browser/devtools/webconsole/test/browser_webconsole_bug_601177_log_levels.js
new file mode 100644
index 000000000..dbe21f32d
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_601177_log_levels.js
@@ -0,0 +1,73 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 601177: log levels";
+const TEST_URI2 = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-601177-log-levels.html";
+
+let test = asyncTest(function* () {
+ Services.prefs.setBoolPref("javascript.options.strict", true);
+
+ let { browser } = yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ expectUncaughtException();
+
+ yield testLogLevels(hud);
+
+ Services.prefs.clearUserPref("javascript.options.strict");
+});
+
+function testLogLevels(hud) {
+ content.location = TEST_URI2;
+
+ info("waiting for messages");
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ text: "test-bug-601177-log-levels.html",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "test-bug-601177-log-levels.js",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "test-image.png",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "foobar-known-to-fail.png",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_ERROR,
+ },
+ {
+ text: "foobarBug601177exception",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ },
+ {
+ text: "undefinedPropertyBug601177",
+ category: CATEGORY_JS,
+ severity: SEVERITY_WARNING,
+ },
+ {
+ text: "foobarBug601177strictError",
+ category: CATEGORY_JS,
+ severity: SEVERITY_WARNING,
+ },
+ ],
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_601352_scroll.js b/browser/devtools/webconsole/test/browser_webconsole_bug_601352_scroll.js
new file mode 100644
index 000000000..2ff267896
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_601352_scroll.js
@@ -0,0 +1,69 @@
+/* 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 that the console output scrolls to JS eval results when there are many
+// messages displayed. See bug 601352.
+
+function test() {
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ let {tab} = yield loadTab("data:text/html;charset=utf-8,Web Console test for bug 601352");
+ let hud = yield openConsole(tab);
+ hud.jsterm.clearOutput();
+
+ let longMessage = "";
+ for (let i = 0; i < 50; i++) {
+ longMessage += "LongNonwrappingMessage";
+ }
+
+ for (let i = 0; i < 50; i++) {
+ content.console.log("test1 message " + i);
+ }
+
+ content.console.log(longMessage);
+
+ for (let i = 0; i < 50; i++) {
+ content.console.log("test2 message " + i);
+ }
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "test1 message 0",
+ }, {
+ text: "test1 message 49",
+ }, {
+ text: "LongNonwrappingMessage",
+ }, {
+ text: "test2 message 0",
+ }, {
+ text: "test2 message 49",
+ }],
+ });
+
+ let node = yield hud.jsterm.execute("1+1");
+
+ let scrollNode = hud.outputNode.parentNode;
+ let rectNode = node.getBoundingClientRect();
+ let rectOutput = scrollNode.getBoundingClientRect();
+ console.debug("rectNode", rectNode, "rectOutput", rectOutput);
+ console.log("scrollNode scrollHeight", scrollNode.scrollHeight, "scrollTop", scrollNode.scrollTop, "clientHeight", scrollNode.clientHeight);
+
+ isnot(scrollNode.scrollTop, 0, "scroll location is not at the top");
+
+ // The bounding client rect .top/left coordinates are relative to the
+ // console iframe.
+
+ // Visible scroll viewport.
+ let height = rectOutput.height;
+
+ // Top and bottom coordinates of the last message node, relative to the outputNode.
+ let top = rectNode.top - rectOutput.top;
+ let bottom = top + rectNode.height;
+ info("node top " + top + " node bottom " + bottom + " node clientHeight " + node.clientHeight);
+
+ ok(top >= 0 && bottom <= height, "last message is visible");
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js b/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js
new file mode 100644
index 000000000..d00f2d2d9
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_601667_filter_buttons.js
@@ -0,0 +1,241 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the filter button UI logic works correctly.
+
+const TEST_URI = "http://example.com/";
+
+let hud, hudId, hudBox;
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ hud = yield openConsole();
+ hudId = hud.hudId;
+ hudBox = hud.ui.rootElement;
+
+ testFilterButtons();
+
+ hud = hudId = hudBox = null;
+});
+
+function testFilterButtons() {
+ testMenuFilterButton("net");
+ testMenuFilterButton("css");
+ testMenuFilterButton("js");
+ testMenuFilterButton("logging");
+ testMenuFilterButton("security");
+
+ testIsolateFilterButton("net");
+ testIsolateFilterButton("css");
+ testIsolateFilterButton("js");
+ testIsolateFilterButton("logging");
+ testIsolateFilterButton("security");
+}
+
+function testMenuFilterButton(aCategory) {
+ let selector = ".webconsole-filter-button[category=\"" + aCategory + "\"]";
+ let button = hudBox.querySelector(selector);
+ ok(button, "we have the \"" + aCategory + "\" button");
+
+ let firstMenuItem = button.querySelector("menuitem");
+ ok(firstMenuItem, "we have the first menu item for the \"" + aCategory +
+ "\" button");
+
+ // Turn all the filters off, if they were on.
+ let menuItem = firstMenuItem;
+ while (menuItem != null) {
+ if (menuItem.hasAttribute("prefKey") && isChecked(menuItem)) {
+ chooseMenuItem(menuItem);
+ }
+ menuItem = menuItem.nextSibling;
+ }
+
+ // Turn all the filters on; make sure the button gets checked.
+ menuItem = firstMenuItem;
+ let prefKey;
+ while (menuItem) {
+ if (menuItem.hasAttribute("prefKey")) {
+ prefKey = menuItem.getAttribute("prefKey");
+ chooseMenuItem(menuItem);
+ ok(isChecked(menuItem), "menu item " + prefKey + " for category " +
+ aCategory + " is checked after clicking it");
+ ok(hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+ "on after clicking the appropriate menu item");
+ }
+ menuItem = menuItem.nextSibling;
+ }
+ ok(isChecked(button), "the button for category " + aCategory + " is " +
+ "checked after turning on all its menu items");
+
+ // Turn one filter off; make sure the button is still checked.
+ prefKey = firstMenuItem.getAttribute("prefKey");
+ chooseMenuItem(firstMenuItem);
+ ok(!isChecked(firstMenuItem), "the first menu item for category " +
+ aCategory + " is no longer checked after clicking it");
+ ok(!hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+ "turned off after clicking the appropriate menu item");
+ ok(isChecked(button), "the button for category " + aCategory + " is still " +
+ "checked after turning off its first menu item");
+
+ // Turn all the filters off by clicking the main part of the button.
+ let subbutton = getMainButton(button);
+ ok(subbutton, "we have the subbutton for category " + aCategory);
+
+ clickButton(subbutton);
+ ok(!isChecked(button), "the button for category " + aCategory + " is " +
+ "no longer checked after clicking its main part");
+
+ menuItem = firstMenuItem;
+ while (menuItem) {
+ let prefKey = menuItem.getAttribute("prefKey");
+ if (prefKey) {
+ ok(!isChecked(menuItem), "menu item " + prefKey + " for category " +
+ aCategory + " is no longer checked after clicking the button");
+ ok(!hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+ "off after clicking the button");
+ }
+ menuItem = menuItem.nextSibling;
+ }
+
+ // Turn all the filters on by clicking the main part of the button.
+ clickButton(subbutton);
+
+ ok(isChecked(button), "the button for category " + aCategory + " is " +
+ "checked after clicking its main part");
+
+ menuItem = firstMenuItem;
+ while (menuItem) {
+ if (menuItem.hasAttribute("prefKey")) {
+ let prefKey = menuItem.getAttribute("prefKey");
+ // The CSS/Log menu item should not be checked. See bug 971798.
+ if (aCategory == "css" && prefKey == "csslog") {
+ ok(!isChecked(menuItem), "menu item " + prefKey + " for category " +
+ aCategory + " should not be checked after clicking the button");
+ ok(!hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+ "off after clicking the button");
+ } else {
+ ok(isChecked(menuItem), "menu item " + prefKey + " for category " +
+ aCategory + " is checked after clicking the button");
+ ok(hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+ "on after clicking the button");
+ }
+ }
+ menuItem = menuItem.nextSibling;
+ }
+
+ // Uncheck the main button by unchecking all the filters
+ menuItem = firstMenuItem;
+ while (menuItem) {
+ // The csslog menu item is already unchecked at this point.
+ // Make sure it is not selected. See bug 971798.
+ prefKey = menuItem.getAttribute("prefKey");
+ if (prefKey && prefKey != "csslog") {
+ chooseMenuItem(menuItem);
+ }
+ menuItem = menuItem.nextSibling;
+ }
+
+ ok(!isChecked(button), "the button for category " + aCategory + " is " +
+ "unchecked after unchecking all its filters");
+
+ // Turn all the filters on again by clicking the button.
+ clickButton(subbutton);
+}
+
+function testIsolateFilterButton(aCategory) {
+ let selector = ".webconsole-filter-button[category=\"" + aCategory + "\"]";
+ let targetButton = hudBox.querySelector(selector);
+ ok(targetButton, "we have the \"" + aCategory + "\" button");
+
+ // Get the main part of the filter button.
+ let subbutton = getMainButton(targetButton);
+ ok(subbutton, "we have the subbutton for category " + aCategory);
+
+ // Turn on all the filters by alt clicking the main part of the button.
+ altClickButton(subbutton);
+ ok(isChecked(targetButton), "the button for category " + aCategory +
+ " is checked after isolating for filter");
+
+ // Check if all the filters for the target button are on.
+ let menuItems = targetButton.querySelectorAll("menuitem");
+ Array.forEach(menuItems, (item) => {
+ let prefKey = item.getAttribute("prefKey");
+ // The CSS/Log filter should not be checked. See bug 971798.
+ if (aCategory == "css" && prefKey == "csslog") {
+ ok(!isChecked(item), "menu item " + prefKey + " for category " +
+ aCategory + " should not be checked after isolating for " + aCategory);
+ ok(!hud.ui.filterPrefs[prefKey], prefKey + " messages should be " +
+ "turned off after isolating for " + aCategory);
+ } else if (prefKey) {
+ ok(isChecked(item), "menu item " + prefKey + " for category " +
+ aCategory + " is checked after isolating for " + aCategory);
+ ok(hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+ "turned on after isolating for " + aCategory);
+ }
+ });
+
+ // Ensure all other filter buttons are toggled off and their
+ // associated filters are turned off
+ let buttons = hudBox.querySelectorAll(".webconsole-filter-button[category]");
+ Array.forEach(buttons, (filterButton) => {
+ if (filterButton !== targetButton) {
+ let category = filterButton.getAttribute("category");
+ ok(!isChecked(filterButton), "the button for category " +
+ category + " is unchecked after isolating for " + aCategory);
+
+ menuItems = filterButton.querySelectorAll("menuitem");
+ Array.forEach(menuItems, (item) => {
+ let prefKey = item.getAttribute("prefKey");
+ if (prefKey) {
+ ok(!isChecked(item), "menu item " + prefKey + " for category " +
+ aCategory + " is unchecked after isolating for " + aCategory);
+ ok(!hud.ui.filterPrefs[prefKey], prefKey + " messages are " +
+ "turned off after isolating for " + aCategory);
+ }
+ });
+
+ // Turn all the filters on again by clicking the button.
+ let mainButton = getMainButton(filterButton);
+ clickButton(mainButton);
+ }
+ });
+}
+
+/**
+ * Return the main part of the target filter button.
+ */
+function getMainButton(aTargetButton) {
+ let anonymousNodes = hud.ui.document.getAnonymousNodes(aTargetButton);
+ let subbutton;
+
+ for (let i = 0; i < anonymousNodes.length; i++) {
+ let node = anonymousNodes[i];
+ if (node.classList.contains("toolbarbutton-menubutton-button")) {
+ subbutton = node;
+ break;
+ }
+ }
+
+ return subbutton;
+}
+
+function clickButton(aNode) {
+ EventUtils.sendMouseEvent({ type: "click" }, aNode);
+}
+
+function altClickButton(aNode) {
+ EventUtils.sendMouseEvent({ type: "click", altKey: true }, aNode);
+}
+
+function chooseMenuItem(aNode) {
+ let event = document.createEvent("XULCommandEvent");
+ event.initCommandEvent("command", true, true, window, 0, false, false, false,
+ false, null);
+ aNode.dispatchEvent(event);
+}
+
+function isChecked(aNode) {
+ return aNode.getAttribute("checked") === "true";
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_602572_log_bodies_checkbox.js b/browser/devtools/webconsole/test/browser_webconsole_bug_602572_log_bodies_checkbox.js
new file mode 100644
index 000000000..ae68ecda8
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_602572_log_bodies_checkbox.js
@@ -0,0 +1,177 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+let menuitems = [], menupopups = [], huds = [], tabs = [], runCount = 0;
+
+const TEST_URI1 = "data:text/html;charset=utf-8,Web Console test for bug 602572: log bodies checkbox. tab 1";
+const TEST_URI2 = "data:text/html;charset=utf-8,Web Console test for bug 602572: log bodies checkbox. tab 2";
+
+function test()
+{
+ if (runCount == 0) {
+ requestLongerTimeout(2);
+ }
+
+ // open tab 1
+ loadTab(TEST_URI1).then((tab) => {
+ tabs.push(tab.tab);
+ openConsole().then((hud) => {
+ hud.iframeWindow.mozRequestAnimationFrame(() => {
+ info("iframe1 root height " + hud.ui.rootElement.clientHeight);
+
+ // open tab 2
+ loadTab(TEST_URI2).then((tab) => {
+ tabs.push(tab.tab);
+ openConsole().then((hud) => hud.iframeWindow.mozRequestAnimationFrame(startTest));
+ });
+ });
+ });
+ });
+}
+
+function startTest()
+{
+ // Find the relevant elements in the Web Console of tab 2.
+ let win2 = tabs[runCount*2 + 1].linkedBrowser.contentWindow;
+ huds[1] = HUDService.getHudByWindow(win2);
+ info("startTest: iframe2 root height " + huds[1].ui.rootElement.clientHeight);
+
+ if (runCount == 0) {
+ menuitems[1] = huds[1].ui.rootElement.querySelector("#saveBodies");
+ }
+ else {
+ menuitems[1] = huds[1].ui.rootElement.querySelector("#saveBodiesContextMenu");
+ }
+ menupopups[1] = menuitems[1].parentNode;
+
+ // Open the context menu from tab 2.
+ menupopups[1].addEventListener("popupshown", onpopupshown2, false);
+ executeSoon(function() {
+ menupopups[1].openPopup();
+ });
+}
+
+function onpopupshown2(aEvent)
+{
+ menupopups[1].removeEventListener(aEvent.type, onpopupshown2, false);
+
+ // By default bodies are not logged.
+ isnot(menuitems[1].getAttribute("checked"), "true",
+ "menuitems[1] is not checked");
+
+ ok(!huds[1].ui._saveRequestAndResponseBodies, "bodies are not logged");
+
+ // Enable body logging.
+ huds[1].ui.setSaveRequestAndResponseBodies(true).then(() => {
+ menupopups[1].hidePopup();
+ });
+
+ menupopups[1].addEventListener("popuphidden", function _onhidden(aEvent) {
+ menupopups[1].removeEventListener(aEvent.type, _onhidden, false);
+
+ info("menupopups[1] hidden");
+
+ // Reopen the context menu.
+ huds[1].ui.once("save-bodies-ui-toggled", () => testpopup2b(aEvent));
+ menupopups[1].openPopup();
+ }, false);
+}
+
+function testpopup2b(aEvent) {
+ is(menuitems[1].getAttribute("checked"), "true", "menuitems[1] is checked");
+
+ menupopups[1].addEventListener("popuphidden", function _onhidden(aEvent) {
+ menupopups[1].removeEventListener(aEvent.type, _onhidden, false);
+
+ info("menupopups[1] hidden");
+
+ // Switch to tab 1 and open the Web Console context menu from there.
+ gBrowser.selectedTab = tabs[runCount*2];
+ waitForFocus(function() {
+ // Find the relevant elements in the Web Console of tab 1.
+ let win1 = tabs[runCount*2].linkedBrowser.contentWindow;
+ huds[0] = HUDService.getHudByWindow(win1);
+
+ info("iframe1 root height " + huds[0].ui.rootElement.clientHeight);
+
+ menuitems[0] = huds[0].ui.rootElement.querySelector("#saveBodies");
+ menupopups[0] = huds[0].ui.rootElement.querySelector("menupopup");
+
+ menupopups[0].addEventListener("popupshown", onpopupshown1, false);
+ executeSoon(() => menupopups[0].openPopup());
+ }, tabs[runCount*2].linkedBrowser.contentWindow);
+ }, false);
+
+ executeSoon(function() {
+ menupopups[1].hidePopup();
+ });
+}
+
+function onpopupshown1(aEvent)
+{
+ menupopups[0].removeEventListener(aEvent.type, onpopupshown1, false);
+
+ // The menuitem checkbox must not be in sync with the other tabs.
+ isnot(menuitems[0].getAttribute("checked"), "true",
+ "menuitems[0] is not checked");
+
+ // Enable body logging for tab 1 as well.
+ huds[0].ui.setSaveRequestAndResponseBodies(true).then(() => {
+ menupopups[0].hidePopup();
+ });
+
+ // Close the menu, and switch back to tab 2.
+ menupopups[0].addEventListener("popuphidden", function _onhidden(aEvent) {
+ menupopups[0].removeEventListener(aEvent.type, _onhidden, false);
+
+ info("menupopups[0] hidden");
+
+ gBrowser.selectedTab = tabs[runCount*2 + 1];
+ waitForFocus(function() {
+ // Reopen the context menu from tab 2.
+ huds[1].ui.once("save-bodies-ui-toggled", () => testpopup2c(aEvent));
+ menupopups[1].openPopup();
+ }, tabs[runCount*2 + 1].linkedBrowser.contentWindow);
+ }, false);
+}
+
+function testpopup2c(aEvent) {
+ is(menuitems[1].getAttribute("checked"), "true", "menuitems[1] is checked");
+
+ menupopups[1].addEventListener("popuphidden", function _onhidden(aEvent) {
+ menupopups[1].removeEventListener(aEvent.type, _onhidden, false);
+
+ info("menupopups[1] hidden");
+
+ // Done if on second run
+ closeConsole(gBrowser.selectedTab).then(function() {
+ if (runCount == 0) {
+ runCount++;
+ info("start second run");
+ executeSoon(test);
+ }
+ else {
+ gBrowser.removeCurrentTab();
+ gBrowser.selectedTab = tabs[2];
+ gBrowser.removeCurrentTab();
+ gBrowser.selectedTab = tabs[1];
+ gBrowser.removeCurrentTab();
+ gBrowser.selectedTab = tabs[0];
+ gBrowser.removeCurrentTab();
+ huds = menuitems = menupopups = tabs = null;
+ executeSoon(finishTest);
+ }
+ });
+ }, false);
+
+ executeSoon(function() {
+ menupopups[1].hidePopup();
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_603750_websocket.js b/browser/devtools/webconsole/test/browser_webconsole_bug_603750_websocket.js
new file mode 100644
index 000000000..5bc9c3a12
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_603750_websocket.js
@@ -0,0 +1,37 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-603750-websocket.html";
+const TEST_URI2 = "data:text/html;charset=utf-8,Web Console test for bug 603750: Web Socket errors";
+
+let test = asyncTest(function* () {
+ let { browser } = yield loadTab(TEST_URI2);
+
+ let hud = yield openConsole();
+
+ content.location = TEST_URI;
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ text: "ws://0.0.0.0:81",
+ source: { url: "test-bug-603750-websocket.js" },
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ },
+ {
+ text: "ws://0.0.0.0:82",
+ source: { url: "test-bug-603750-websocket.js" },
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ },
+ ]});
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_611795.js b/browser/devtools/webconsole/test/browser_webconsole_bug_611795.js
new file mode 100644
index 000000000..cfc1c8c37
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_611795.js
@@ -0,0 +1,64 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URI = 'data:text/html;charset=utf-8,<div style="-moz-opacity:0;">test repeated' +
+ ' css warnings</div><p style="-moz-opacity:0">hi</p>';
+let hud;
+
+"use strict";
+
+/**
+ * Unit test for bug 611795:
+ * Repeated CSS messages get collapsed into one.
+ */
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ hud = yield openConsole();
+ hud.jsterm.clearOutput(true);
+
+ BrowserReload();
+ yield loadBrowser(gBrowser.selectedBrowser);
+
+ yield onContentLoaded();
+ yield testConsoleLogRepeats();
+
+ hud = null;
+});
+
+function onContentLoaded()
+{
+ let cssWarning = "Unknown property '-moz-opacity'. Declaration dropped.";
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: cssWarning,
+ category: CATEGORY_CSS,
+ severity: SEVERITY_WARNING,
+ repeats: 2,
+ }],
+ });
+}
+
+function testConsoleLogRepeats()
+{
+ let jsterm = hud.jsterm;
+
+ jsterm.clearOutput();
+
+ jsterm.setInputValue("for (let i = 0; i < 10; ++i) console.log('this is a line of reasonably long text that I will use to verify that the repeated text node is of an appropriate size.');");
+ jsterm.execute();
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "this is a line of reasonably long text",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ repeats: 10,
+ }],
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_613013_console_api_iframe.js b/browser/devtools/webconsole/test/browser_webconsole_bug_613013_console_api_iframe.js
new file mode 100644
index 000000000..13a1057ec
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_613013_console_api_iframe.js
@@ -0,0 +1,29 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-613013-console-api-iframe.html";
+
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ BrowserReload();
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "foobarBug613013",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_613280_jsterm_copy.js b/browser/devtools/webconsole/test/browser_webconsole_bug_613280_jsterm_copy.js
new file mode 100644
index 000000000..d35559ab5
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_613280_jsterm_copy.js
@@ -0,0 +1,80 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ */
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 613280";
+
+function test() {
+ loadTab(TEST_URI).then(() => {
+ openConsole().then((HUD) => {
+ content.console.log("foobarBazBug613280");
+ waitForMessages({
+ webconsole: HUD,
+ messages: [{
+ text: "foobarBazBug613280",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ }).then(performTest.bind(null, HUD));
+ })
+ });
+}
+
+function performTest(HUD, [result]) {
+ let msg = [...result.matched][0];
+ let input = HUD.jsterm.inputNode;
+ let selection = getSelection();
+ let contentSelection = content.getSelection();
+
+ let clipboard_setup = function() {
+ goDoCommand("cmd_copy");
+ };
+
+ let clipboard_copy_done = function() {
+ finishTest();
+ };
+
+ // Check if we first need to clear any existing selections.
+ if (selection.rangeCount > 0 || contentSelection.rangeCount > 0 ||
+ input.selectionStart != input.selectionEnd) {
+ if (input.selectionStart != input.selectionEnd) {
+ input.selectionStart = input.selectionEnd = 0;
+ }
+
+ if (selection.rangeCount > 0) {
+ selection.removeAllRanges();
+ }
+
+ if (contentSelection.rangeCount > 0) {
+ contentSelection.removeAllRanges();
+ }
+
+ goUpdateCommand("cmd_copy");
+ }
+
+ let controller = top.document.commandDispatcher.
+ getControllerForCommand("cmd_copy");
+ is(controller.isCommandEnabled("cmd_copy"), false, "cmd_copy is disabled");
+
+ HUD.ui.output.selectMessage(msg);
+ HUD.outputNode.focus();
+
+ goUpdateCommand("cmd_copy");
+
+ controller = top.document.commandDispatcher.
+ getControllerForCommand("cmd_copy");
+ is(controller.isCommandEnabled("cmd_copy"), true, "cmd_copy is enabled");
+
+ // Remove new lines since getSelection() includes one between message and line
+ // number, but the clipboard doesn't (see bug 1119503)
+ let selectionText = (HUD.iframeWindow.getSelection() + "").replace(/\r?\n|\r/g, " ");
+ isnot(selectionText.indexOf("foobarBazBug613280"), -1,
+ "selection text includes 'foobarBazBug613280'");
+
+ waitForClipboard((str) => { return str.trim() == selectionText.trim(); },
+ clipboard_setup, clipboard_copy_done, clipboard_copy_done);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_613642_maintain_scroll.js b/browser/devtools/webconsole/test/browser_webconsole_bug_613642_maintain_scroll.js
new file mode 100644
index 000000000..c747ca0a8
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_613642_maintain_scroll.js
@@ -0,0 +1,116 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ */
+
+let TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 613642: remember scroll location";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ hud.jsterm.clearOutput();
+ let outputNode = hud.outputNode;
+ let scrollBox = outputNode.parentNode;
+
+ for (let i = 0; i < 150; i++) {
+ content.console.log("test message " + i);
+ }
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "test message 149",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ ok(scrollBox.scrollTop > 0, "scroll location is not at the top");
+
+ // scroll to the first node
+ outputNode.focus();
+
+ let scrolled = promise.defer();
+
+ scrollBox.onscroll = () => {
+ info("onscroll top " + scrollBox.scrollTop);
+ if (scrollBox.scrollTop != 0) {
+ // Wait for scroll to 0.
+ return;
+ }
+ scrollBox.onscroll = null;
+ is(scrollBox.scrollTop, 0, "scroll location updated (moved to top)");
+ scrolled.resolve();
+ };
+ EventUtils.synthesizeKey("VK_HOME", {}, hud.iframeWindow);
+
+ yield scrolled.promise;
+
+
+ // add a message and make sure scroll doesn't change
+ content.console.log("test message 150");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "test message 150",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ scrolled = promise.defer();
+ scrollBox.onscroll = () => {
+ if (scrollBox.scrollTop != 0) {
+ // Wait for scroll to stabilize at the top.
+ return;
+ }
+ scrollBox.onscroll = null;
+ is(scrollBox.scrollTop, 0, "scroll location is still at the top");
+ scrolled.resolve();
+ };
+
+ // Make sure that scroll stabilizes at the top. executeSoon() is needed for
+ // the yield to work.
+ executeSoon(scrollBox.onscroll);
+
+ yield scrolled.promise;
+
+ // scroll back to the bottom
+ outputNode.lastChild.focus();
+
+ scrolled = promise.defer();
+ scrollBox.onscroll = () => {
+ if (scrollBox.scrollTop == 0) {
+ // Wait for scroll to bottom.
+ return;
+ }
+ scrollBox.onscroll = null;
+ isnot(scrollBox.scrollTop, 0, "scroll location updated (moved to bottom)");
+ scrolled.resolve();
+ };
+ EventUtils.synthesizeKey("VK_END", {});
+ yield scrolled.promise;
+
+ let oldScrollTop = scrollBox.scrollTop;
+
+ content.console.log("test message 151");
+
+ scrolled = promise.defer();
+ scrollBox.onscroll = () => {
+ if (scrollBox.scrollTop == oldScrollTop) {
+ // Wait for scroll to change.
+ return;
+ }
+ scrollBox.onscroll = null;
+ isnot(scrollBox.scrollTop, oldScrollTop, "scroll location updated (moved to bottom again)");
+ scrolled.resolve();
+ };
+ yield scrolled.promise;
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_613642_prune_scroll.js b/browser/devtools/webconsole/test/browser_webconsole_bug_613642_prune_scroll.js
new file mode 100644
index 000000000..c11ca8878
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_613642_prune_scroll.js
@@ -0,0 +1,81 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ */
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 613642: maintain scroll with pruning of old messages";
+
+let hud;
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ hud.jsterm.clearOutput();
+
+ let outputNode = hud.outputNode;
+
+ Services.prefs.setIntPref("devtools.hud.loglimit.console", 140);
+ let scrollBoxElement = outputNode.parentNode;
+
+ for (let i = 0; i < 150; i++) {
+ content.console.log("test message " + i);
+ }
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "test message 149",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ let oldScrollTop = scrollBoxElement.scrollTop;
+ isnot(oldScrollTop, 0, "scroll location is not at the top");
+
+ let firstNode = outputNode.firstChild;
+ ok(firstNode, "found the first message");
+
+ let msgNode = outputNode.children[80];
+ ok(msgNode, "found the 80th message");
+
+ // scroll to the middle message node
+ msgNode.scrollIntoView(false);
+
+ isnot(scrollBoxElement.scrollTop, oldScrollTop,
+ "scroll location updated (scrolled to message)");
+
+ oldScrollTop = scrollBoxElement.scrollTop;
+
+ // add a message
+ content.console.log("hello world");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "hello world",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ // Scroll location needs to change, because one message is also removed, and
+ // we need to scroll a bit towards the top, to keep the current view in sync.
+ isnot(scrollBoxElement.scrollTop, oldScrollTop,
+ "scroll location updated (added a message)");
+
+ isnot(outputNode.firstChild, firstNode,
+ "first message removed");
+
+ Services.prefs.clearUserPref("devtools.hud.loglimit.console");
+
+ hud = null;
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_614793_jsterm_scroll.js b/browser/devtools/webconsole/test/browser_webconsole_bug_614793_jsterm_scroll.js
new file mode 100644
index 000000000..05dcb76fa
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_614793_jsterm_scroll.js
@@ -0,0 +1,63 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ */
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 614793: jsterm result scroll";
+
+"use strict";
+
+let test = asyncTest(function* () {
+ let { browser } = yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ yield consoleOpened(hud);
+});
+
+function consoleOpened(hud) {
+ let deferred = promise.defer();
+
+ hud.jsterm.clearOutput();
+
+ let scrollNode = hud.outputNode.parentNode;
+
+ for (let i = 0; i < 150; i++) {
+ content.console.log("test message " + i);
+ }
+
+ let oldScrollTop = -1;
+
+ waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "test message 149",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ }).then(() => {
+ oldScrollTop = scrollNode.scrollTop;
+ isnot(oldScrollTop, 0, "scroll location is not at the top");
+
+ hud.jsterm.execute("'hello world'").then(onExecute);
+ });
+
+ function onExecute(msg)
+ {
+ isnot(scrollNode.scrollTop, oldScrollTop, "scroll location updated");
+
+ oldScrollTop = scrollNode.scrollTop;
+
+ msg.scrollIntoView(false);
+
+ is(scrollNode.scrollTop, oldScrollTop, "scroll location is the same");
+
+ deferred.resolve();
+ }
+
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_618078_network_exceptions.js b/browser/devtools/webconsole/test/browser_webconsole_bug_618078_network_exceptions.js
new file mode 100644
index 000000000..cb98e2c0d
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_618078_network_exceptions.js
@@ -0,0 +1,29 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that we report JS exceptions in event handlers coming from
+// network requests, like onreadystate for XHR. See bug 618078.
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 618078";
+const TEST_URI2 = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-618078-network-exceptions.html";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ expectUncaughtException();
+
+ content.location = TEST_URI2;
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "bug618078exception",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ }],
+ });
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_618311_close_panels.js b/browser/devtools/webconsole/test/browser_webconsole_bug_618311_close_panels.js
new file mode 100644
index 000000000..6d3c9e4e5
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_618311_close_panels.js
@@ -0,0 +1,90 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ BrowserReload();
+
+ let results = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "test-console.html",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ }],
+ })
+
+ yield performTest(hud, results);
+});
+
+
+function performTest(HUD, results) {
+ let deferred = promise.defer();
+
+ let networkMessage = [...results[0].matched][0];
+ ok(networkMessage, "network message element");
+
+ let networkLink = networkMessage.querySelector(".url");
+ ok(networkLink, "found network message link");
+
+ let popupset = document.getElementById("mainPopupSet");
+ ok(popupset, "found #mainPopupSet");
+
+ let popupsShown = 0;
+ let hiddenPopups = 0;
+
+ let onpopupshown = function() {
+ document.removeEventListener("popupshown", onpopupshown, false);
+ popupsShown++;
+
+ executeSoon(function() {
+ let popups = popupset.querySelectorAll("panel[hudId=" + HUD.hudId + "]");
+ is(popups.length, 1, "found one popup");
+
+ document.addEventListener("popuphidden", onpopuphidden, false);
+
+ registerCleanupFunction(function() {
+ is(hiddenPopups, 1, "correct number of popups hidden");
+ if (hiddenPopups != 1) {
+ document.removeEventListener("popuphidden", onpopuphidden, false);
+ }
+ });
+
+ executeSoon(closeConsole);
+ });
+ };
+
+ let onpopuphidden = function() {
+ document.removeEventListener("popuphidden", onpopuphidden, false);
+ hiddenPopups++;
+
+ executeSoon(function() {
+ let popups = popupset.querySelectorAll("panel[hudId=" + HUD.hudId + "]");
+ is(popups.length, 0, "no popups found");
+
+ executeSoon(deferred.resolve);
+ });
+ };
+
+ document.addEventListener("popupshown", onpopupshown, false);
+
+ registerCleanupFunction(function() {
+ is(popupsShown, 1, "correct number of popups shown");
+ if (popupsShown != 1) {
+ document.removeEventListener("popupshown", onpopupshown, false);
+ }
+ });
+
+ EventUtils.sendMouseEvent({ type: "mousedown" }, networkLink, HUD.iframeWindow);
+ EventUtils.sendMouseEvent({ type: "mouseup" }, networkLink, HUD.iframeWindow);
+ EventUtils.sendMouseEvent({ type: "click" }, networkLink, HUD.iframeWindow);
+
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_621644_jsterm_dollar.js b/browser/devtools/webconsole/test/browser_webconsole_bug_621644_jsterm_dollar.js
new file mode 100644
index 000000000..085887e69
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_621644_jsterm_dollar.js
@@ -0,0 +1,49 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Sucan <mihai.sucan@gmail.com>
+ */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-621644-jsterm-dollar.html";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ yield test$(hud);
+ yield test$$(hud);
+});
+
+
+function* test$(HUD) {
+ let deferred = promise.defer();
+
+ HUD.jsterm.clearOutput();
+
+ HUD.jsterm.execute("$(document.body)", (msg) => {
+ ok(msg.textContent.indexOf("<p>") > -1,
+ "jsterm output is correct for $()");
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function test$$(HUD) {
+ let deferred = promise.defer();
+
+ HUD.jsterm.clearOutput();
+
+ HUD.jsterm.setInputValue();
+ HUD.jsterm.execute("$$(document)", (msg) => {
+ ok(msg.textContent.indexOf("621644") > -1,
+ "jsterm output is correct for $$()");
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_622303_persistent_filters.js b/browser/devtools/webconsole/test/browser_webconsole_bug_622303_persistent_filters.js
new file mode 100644
index 000000000..80966a691
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_622303_persistent_filters.js
@@ -0,0 +1,139 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const prefs = {
+ "net": [
+ "network",
+ "netwarn",
+ "netxhr",
+ "networkinfo"
+ ],
+ "css": [
+ "csserror",
+ "cssparser",
+ "csslog"
+ ],
+ "js": [
+ "exception",
+ "jswarn",
+ "jslog",
+ ],
+ "logging": [
+ "error",
+ "warn",
+ "info",
+ "log"
+ ]
+};
+
+let test = asyncTest(function* () {
+ // Set all prefs to true
+ for (let category in prefs) {
+ prefs[category].forEach(function(pref) {
+ Services.prefs.setBoolPref("devtools.webconsole.filter." + pref, true);
+ });
+ }
+
+ yield loadTab("about:blank");
+
+ let hud = yield openConsole();
+
+ let hud2 = yield onConsoleOpen(hud);
+ let hud3 = yield onConsoleReopen1(hud2);
+ yield onConsoleReopen2(hud3);
+
+ // Clear prefs
+ for (let category in prefs) {
+ prefs[category].forEach(function(pref) {
+ Services.prefs.clearUserPref("devtools.webconsole.filter." + pref);
+ });
+ }
+});
+
+function onConsoleOpen(hud) {
+ let deferred = promise.defer();
+
+ let hudBox = hud.ui.rootElement;
+
+ // Check if the filters menuitems exists and are checked
+ for (let category in prefs) {
+ let button = hudBox.querySelector(".webconsole-filter-button[category=\""
+ + category + "\"]");
+ ok(isChecked(button), "main button for " + category + " category is checked");
+
+ prefs[category].forEach(function(pref) {
+ let menuitem = hudBox.querySelector("menuitem[prefKey=" + pref + "]");
+ ok(isChecked(menuitem), "menuitem for " + pref + " is checked");
+ });
+ }
+
+ // Set all prefs to false
+ for (let category in prefs) {
+ prefs[category].forEach(function(pref) {
+ hud.setFilterState(pref, false);
+ });
+ }
+
+ //Re-init the console
+ closeConsole().then(() => {
+ openConsole().then(deferred.resolve);
+ });
+
+ return deferred.promise;
+}
+
+function onConsoleReopen1(hud) {
+ info("testing after reopening once");
+ let deferred = promise.defer();
+
+ let hudBox = hud.ui.rootElement;
+
+ // Check if the filter button and menuitems are unchecked
+ for (let category in prefs) {
+ let button = hudBox.querySelector(".webconsole-filter-button[category=\""
+ + category + "\"]");
+ ok(isUnchecked(button), "main button for " + category + " category is not checked");
+
+ prefs[category].forEach(function(pref) {
+ let menuitem = hudBox.querySelector("menuitem[prefKey=" + pref + "]");
+ ok(isUnchecked(menuitem), "menuitem for " + pref + " is not checked");
+ });
+ }
+
+ // Set first pref in each category to true
+ for (let category in prefs) {
+ hud.setFilterState(prefs[category][0], true);
+ }
+
+ // Re-init the console
+ closeConsole().then(() => {
+ openConsole().then(deferred.resolve);
+ });
+
+ return deferred.promise;
+}
+
+function onConsoleReopen2(hud) {
+ info("testing after reopening again");
+
+ let hudBox = hud.ui.rootElement;
+
+ // Check the main category button is checked and first menuitem is checked
+ for (let category in prefs) {
+ let button = hudBox.querySelector(".webconsole-filter-button[category=\""
+ + category + "\"]");
+ ok(isChecked(button), category + " button is checked when first pref is true");
+
+ let pref = prefs[category][0];
+ let menuitem = hudBox.querySelector("menuitem[prefKey=" + pref + "]");
+ ok(isChecked(menuitem), "first " + category + " menuitem is checked");
+ }
+}
+
+function isChecked(aNode) {
+ return aNode.getAttribute("checked") === "true";
+}
+
+function isUnchecked(aNode) {
+ return aNode.getAttribute("checked") === "false";
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_623749_ctrl_a_select_all_winnt.js b/browser/devtools/webconsole/test/browser_webconsole_bug_623749_ctrl_a_select_all_winnt.js
new file mode 100644
index 000000000..84cd52c4a
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_623749_ctrl_a_select_all_winnt.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test for https://bugzilla.mozilla.org/show_bug.cgi?id=623749
+// Map Control + A to Select All, In the web console input, on Windows
+
+const TEST_URI = "data:text/html;charset=utf-8,Test console for bug 623749";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ let jsterm = hud.jsterm;
+ jsterm.setInputValue("Ignore These Four Words");
+ let inputNode = jsterm.inputNode;
+
+ // Test select all with Control + A.
+ EventUtils.synthesizeKey("a", { ctrlKey: true });
+ let inputLength = inputNode.selectionEnd - inputNode.selectionStart;
+ is(inputLength, inputNode.value.length, "Select all of input");
+
+ // Test do nothing on Control + E.
+ jsterm.setInputValue("Ignore These Four Words");
+ inputNode.selectionStart = 0;
+ EventUtils.synthesizeKey("e", { ctrlKey: true });
+ is(inputNode.selectionStart, 0, "Control + E does not move to end of input");
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_630733_response_redirect_headers.js b/browser/devtools/webconsole/test/browser_webconsole_bug_630733_response_redirect_headers.js
new file mode 100644
index 000000000..88f771275
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_630733_response_redirect_headers.js
@@ -0,0 +1,130 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Mihai Sucan <mihai.sucan@gmail.com>
+ */
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>Web Console test for bug 630733";
+const TEST_URI2 = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-630733-response-redirect-headers.sjs";
+
+let lastFinishedRequests = {};
+let webConsoleClient;
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ yield consoleOpened(hud);
+ yield getHeaders();
+ yield getContent();
+
+ performTest();
+});
+
+function consoleOpened(hud)
+{
+ let deferred = promise.defer();
+
+ webConsoleClient = hud.ui.webConsoleClient;
+ hud.ui.setSaveRequestAndResponseBodies(true).then(() => {
+ ok(hud.ui._saveRequestAndResponseBodies,
+ "The saveRequestAndResponseBodies property was successfully set.");
+
+ HUDService.lastFinishedRequest.callback = (aHttpRequest) => {
+ let status = aHttpRequest.response.status;
+ lastFinishedRequests[status] = aHttpRequest;
+ if ("301" in lastFinishedRequests &&
+ "404" in lastFinishedRequests) {
+ deferred.resolve();
+ }
+ }
+ content.location = TEST_URI2;
+ });
+
+ return deferred.promise;
+}
+
+function getHeaders()
+{
+ let deferred = promise.defer();
+
+ HUDService.lastFinishedRequest.callback = null;
+
+ ok("301" in lastFinishedRequests, "request 1: 301 Moved Permanently");
+ ok("404" in lastFinishedRequests, "request 2: 404 Not found");
+
+ webConsoleClient.getResponseHeaders(lastFinishedRequests["301"].actor,
+ function (aResponse) {
+ lastFinishedRequests["301"].response.headers = aResponse.headers;
+
+ webConsoleClient.getResponseHeaders(lastFinishedRequests["404"].actor,
+ function (aResponse) {
+ lastFinishedRequests["404"].response.headers = aResponse.headers;
+ executeSoon(deferred.resolve);
+ });
+ });
+ return deferred.promise;
+}
+
+function getContent()
+{
+ let deferred = promise.defer();
+
+ webConsoleClient.getResponseContent(lastFinishedRequests["301"].actor,
+ function (aResponse) {
+ lastFinishedRequests["301"].response.content = aResponse.content;
+ lastFinishedRequests["301"].discardResponseBody = aResponse.contentDiscarded;
+
+ webConsoleClient.getResponseContent(lastFinishedRequests["404"].actor,
+ function (aResponse) {
+ lastFinishedRequests["404"].response.content = aResponse.content;
+ lastFinishedRequests["404"].discardResponseBody =
+ aResponse.contentDiscarded;
+
+ webConsoleClient = null;
+ executeSoon(deferred.resolve);
+ });
+ });
+ return deferred.promise;
+}
+
+function performTest()
+{
+ function readHeader(aName)
+ {
+ for (let header of headers) {
+ if (header.name == aName) {
+ return header.value;
+ }
+ }
+ return null;
+ }
+
+ let headers = lastFinishedRequests["301"].response.headers;
+ is(readHeader("Content-Type"), "text/html",
+ "we do have the Content-Type header");
+ is(readHeader("Content-Length"), 71, "Content-Length is correct");
+ is(readHeader("Location"), "/redirect-from-bug-630733",
+ "Content-Length is correct");
+ is(readHeader("x-foobar-bug630733"), "bazbaz",
+ "X-Foobar-bug630733 is correct");
+
+ let body = lastFinishedRequests["301"].response.content;
+ ok(!body.text, "body discarded for request 1");
+ ok(lastFinishedRequests["301"].discardResponseBody,
+ "body discarded for request 1 (confirmed)");
+
+ headers = lastFinishedRequests["404"].response.headers;
+ ok(!readHeader("Location"), "no Location header");
+ ok(!readHeader("x-foobar-bug630733"), "no X-Foobar-bug630733 header");
+
+ body = lastFinishedRequests["404"].response.content.text;
+ isnot(body.indexOf("404"), -1,
+ "body is correct for request 2");
+
+ lastFinishedRequests = webConsoleClient = null;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_632275_getters_document_width.js b/browser/devtools/webconsole/test/browser_webconsole_bug_632275_getters_document_width.js
new file mode 100644
index 000000000..3ee086810
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_632275_getters_document_width.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-632275-getters.html";
+
+let getterValue = null;
+
+function test() {
+ loadTab(TEST_URI).then(() => {
+ openConsole().then(consoleOpened);
+ });
+}
+
+function consoleOpened(hud) {
+ let doc = content.wrappedJSObject.document;
+ getterValue = doc.foobar._val;
+ hud.jsterm.execute("console.dir(document)");
+
+ let onOpen = onViewOpened.bind(null, hud);
+ hud.jsterm.once("variablesview-fetched", onOpen);
+}
+
+function onViewOpened(hud, event, view)
+{
+ let doc = content.wrappedJSObject.document;
+
+ findVariableViewProperties(view, [
+ { name: /^(width|height)$/, dontMatch: 1 },
+ { name: "foobar._val", value: getterValue },
+ { name: "foobar.val", isGetter: true },
+ ], { webconsole: hud }).then(function() {
+ is(doc.foobar._val, getterValue, "getter did not execute");
+ is(doc.foobar.val, getterValue+1, "getter executed");
+ is(doc.foobar._val, getterValue+1, "getter executed (recheck)");
+
+ let textContent = hud.outputNode.textContent;
+ is(textContent.indexOf("document.body.client"), -1,
+ "no document.width/height warning displayed");
+
+ getterValue = null;
+ finishTest();
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js b/browser/devtools/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
new file mode 100644
index 000000000..0410a6968
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_632347_iterators_generators.js
@@ -0,0 +1,81 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-632347-iterators-generators.html";
+
+function test() {
+ requestLongerTimeout(6);
+
+ loadTab(TEST_URI).then(() => {
+ openConsole().then(consoleOpened);
+ });
+}
+
+function consoleOpened(HUD) {
+ let tools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+ let JSPropertyProvider = tools.require("devtools/toolkit/webconsole/utils").JSPropertyProvider;
+
+ let tmp = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
+ tmp.addDebuggerToGlobal(tmp);
+ let dbg = new tmp.Debugger;
+
+ let jsterm = HUD.jsterm;
+ let win = content.wrappedJSObject;
+ let dbgWindow = dbg.makeGlobalObjectReference(win);
+ let container = win._container;
+
+ // Make sure autocomplete does not walk through iterators and generators.
+ let result = container.gen1.next();
+ let completion = JSPropertyProvider(dbgWindow, null, "_container.gen1.");
+ isnot(completion.matches.length, 0, "Got matches for gen1");
+
+ is(result+1, container.gen1.next(), "gen1.next() did not execute");
+
+ result = container.gen2.next();
+
+ completion = JSPropertyProvider(dbgWindow, null, "_container.gen2.");
+ isnot(completion.matches.length, 0, "Got matches for gen2");
+
+ is((result/2+1)*2, container.gen2.next(),
+ "gen2.next() did not execute");
+
+ result = container.iter1.next();
+ is(result[0], "foo", "iter1.next() [0] is correct");
+ is(result[1], "bar", "iter1.next() [1] is correct");
+
+ completion = JSPropertyProvider(dbgWindow, null, "_container.iter1.");
+ isnot(completion.matches.length, 0, "Got matches for iter1");
+
+ result = container.iter1.next();
+ is(result[0], "baz", "iter1.next() [0] is correct");
+ is(result[1], "baaz", "iter1.next() [1] is correct");
+
+ let dbgContent = dbg.makeGlobalObjectReference(content);
+ completion = JSPropertyProvider(dbgContent, null, "_container.iter2.");
+ isnot(completion.matches.length, 0, "Got matches for iter2");
+
+ completion = JSPropertyProvider(dbgWindow, null, "window._container.");
+ ok(completion, "matches available for window._container");
+ ok(completion.matches.length, "matches available for window (length)");
+
+ jsterm.clearOutput();
+
+ jsterm.execute("window._container", (msg) => {
+ jsterm.once("variablesview-fetched", testVariablesView.bind(null, HUD));
+ let anchor = msg.querySelector(".message-body a");
+ EventUtils.synthesizeMouse(anchor, 2, 2, {}, HUD.iframeWindow);
+ });
+}
+
+function testVariablesView(aWebconsole, aEvent, aView) {
+ findVariableViewProperties(aView, [
+ { name: "gen1", isGenerator: true },
+ { name: "gen2", isGenerator: true },
+ { name: "iter1", isIterator: true },
+ { name: "iter2", isIterator: true },
+ ], { webconsole: aWebconsole }).then(function() {
+ executeSoon(finishTest);
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_632817.js b/browser/devtools/webconsole/test/browser_webconsole_bug_632817.js
new file mode 100644
index 000000000..d6dc79bbb
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_632817.js
@@ -0,0 +1,241 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that network log messages bring up the network panel.
+
+const TEST_NETWORK_REQUEST_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-network-request.html";
+
+const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test/test-image.png";
+
+const TEST_DATA_JSON_CONTENT =
+ '{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ] }';
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console network logging tests";
+
+let lastRequest = null;
+let requestCallback = null;
+let hud, browser;
+
+function test()
+{
+ const PREF = "devtools.webconsole.persistlog";
+ const NET_PREF = "devtools.webconsole.filter.networkinfo";
+ const NETXHR_PREF = "devtools.webconsole.filter.netxhr"
+ const MIXED_AC_PREF = "security.mixed_content.block_active_content"
+ let original = Services.prefs.getBoolPref(NET_PREF);
+ let originalXhr = Services.prefs.getBoolPref(NETXHR_PREF);
+ let originalMixedActive = Services.prefs.getBoolPref(MIXED_AC_PREF);
+ Services.prefs.setBoolPref(NET_PREF, true);
+ Services.prefs.setBoolPref(NETXHR_PREF, true);
+ Services.prefs.setBoolPref(MIXED_AC_PREF, false);
+ Services.prefs.setBoolPref(PREF, true);
+ registerCleanupFunction(() => {
+ Services.prefs.setBoolPref(NET_PREF, original);
+ Services.prefs.setBoolPref(NETXHR_PREF, originalXhr);
+ Services.prefs.setBoolPref(MIXED_AC_PREF, originalMixedActive);
+ Services.prefs.clearUserPref(PREF);
+ });
+
+ loadTab(TEST_URI).then((tab) => {
+ browser = tab.browser;
+ openConsole().then((aHud) => {
+ hud = aHud;
+
+ HUDService.lastFinishedRequest.callback = function(aRequest) {
+ lastRequest = aRequest;
+ if (requestCallback) {
+ requestCallback();
+ }
+ };
+
+ executeSoon(testPageLoad);
+ })
+ });
+}
+
+function testPageLoad()
+{
+ requestCallback = function() {
+ // Check if page load was logged correctly.
+ ok(lastRequest, "Page load was logged");
+ is(lastRequest.request.url, TEST_NETWORK_REQUEST_URI,
+ "Logged network entry is page load");
+ is(lastRequest.request.method, "GET", "Method is correct");
+ lastRequest = null;
+ requestCallback = null;
+ executeSoon(testPageLoadBody);
+ };
+
+ content.location = TEST_NETWORK_REQUEST_URI;
+}
+
+function testPageLoadBody()
+{
+ let loaded = false;
+ let requestCallbackInvoked = false;
+
+ // Turn off logging of request bodies and check again.
+ requestCallback = function() {
+ ok(lastRequest, "Page load was logged again");
+ lastRequest = null;
+ requestCallback = null;
+ requestCallbackInvoked = true;
+
+ if (loaded) {
+ executeSoon(testXhrGet);
+ }
+ };
+
+ browser.addEventListener("load", function onLoad() {
+ browser.removeEventListener("load", onLoad, true);
+ loaded = true;
+
+ if (requestCallbackInvoked) {
+ executeSoon(testXhrGet);
+ }
+ }, true);
+
+ content.location.reload();
+}
+
+function testXhrGet()
+{
+ requestCallback = function() {
+ ok(lastRequest, "testXhrGet() was logged");
+ is(lastRequest.request.method, "GET", "Method is correct");
+ lastRequest = null;
+ requestCallback = null;
+ executeSoon(testXhrWarn);
+ };
+
+ // Start the XMLHttpRequest() GET test.
+ content.wrappedJSObject.testXhrGet();
+}
+
+function testXhrWarn()
+{
+ requestCallback = function() {
+ ok(lastRequest, "testXhrWarn() was logged");
+ is(lastRequest.request.method, "GET", "Method is correct");
+ lastRequest = null;
+ requestCallback = null;
+ executeSoon(testXhrPost);
+ };
+
+ // Start the XMLHttpRequest() warn test.
+ content.wrappedJSObject.testXhrWarn();
+}
+
+function testXhrPost()
+{
+ requestCallback = function() {
+ ok(lastRequest, "testXhrPost() was logged");
+ is(lastRequest.request.method, "POST", "Method is correct");
+ lastRequest = null;
+ requestCallback = null;
+ executeSoon(testFormSubmission);
+ };
+
+ // Start the XMLHttpRequest() POST test.
+ content.wrappedJSObject.testXhrPost();
+}
+
+function testFormSubmission()
+{
+ // Start the form submission test. As the form is submitted, the page is
+ // loaded again. Bind to the load event to catch when this is done.
+ requestCallback = function() {
+ ok(lastRequest, "testFormSubmission() was logged");
+ is(lastRequest.request.method, "POST", "Method is correct");
+
+ // There should be 3 network requests pointing to the HTML file.
+ waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ text: "test-network-request.html",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ count: 3,
+ },
+ {
+ text: "test-data.json",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_INFO,
+ isXhr: true,
+ count: 2,
+ },
+ {
+ text: "http://example.com/",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_WARNING,
+ isXhr: true,
+ count: 1,
+ },
+ ],
+ }).then(testLiveFilteringOnSearchStrings);
+ };
+
+ let form = content.document.querySelector("form");
+ ok(form, "we have the HTML form");
+ form.submit();
+}
+
+function testLiveFilteringOnSearchStrings() {
+ setStringFilter("http");
+ isnot(countMessageNodes(), 0, "the log nodes are not hidden when the " +
+ "search string is set to \"http\"");
+
+ setStringFilter("HTTP");
+ isnot(countMessageNodes(), 0, "the log nodes are not hidden when the " +
+ "search string is set to \"HTTP\"");
+
+ setStringFilter("hxxp");
+ is(countMessageNodes(), 0, "the log nodes are hidden when the search " +
+ "string is set to \"hxxp\"");
+
+ setStringFilter("ht tp");
+ isnot(countMessageNodes(), 0, "the log nodes are not hidden when the " +
+ "search string is set to \"ht tp\"");
+
+ setStringFilter("");
+ isnot(countMessageNodes(), 0, "the log nodes are not hidden when the " +
+ "search string is removed");
+
+ setStringFilter("json");
+ is(countMessageNodes(), 2, "the log nodes show only the nodes with \"json\"");
+
+ setStringFilter("'foo'");
+ is(countMessageNodes(), 0, "the log nodes are hidden when searching for " +
+ "the string 'foo'");
+
+ setStringFilter("foo\"bar'baz\"boo'");
+ is(countMessageNodes(), 0, "the log nodes are hidden when searching for " +
+ "the string \"foo\"bar'baz\"boo'\"");
+
+ HUDService.lastFinishedRequest.callback = null;
+ lastRequest = null;
+ requestCallback = null;
+ hud = browser = null;
+ finishTest();
+}
+
+function countMessageNodes() {
+ let messageNodes = hud.outputNode.querySelectorAll(".message");
+ let displayedMessageNodes = 0;
+ let view = hud.iframeWindow;
+ for (let i = 0; i < messageNodes.length; i++) {
+ let computedStyle = view.getComputedStyle(messageNodes[i], null);
+ if (computedStyle.display !== "none")
+ displayedMessageNodes++;
+ }
+
+ return displayedMessageNodes;
+}
+
+function setStringFilter(aValue)
+{
+ hud.ui.filterBox.value = aValue;
+ hud.ui.adjustVisibilityOnSearchStringChange();
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_642108_pruneTest.js b/browser/devtools/webconsole/test/browser_webconsole_bug_642108_pruneTest.js
new file mode 100644
index 000000000..e269b3e99
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_642108_pruneTest.js
@@ -0,0 +1,80 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Tests that the Web Console limits the number of lines displayed according to
+// the user's preferences.
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>test for bug 642108.";
+const LOG_LIMIT = 20;
+
+function test() {
+ let hud;
+
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ let {tab} = yield loadTab(TEST_URI);
+
+ Services.prefs.setIntPref("devtools.hud.loglimit.cssparser", LOG_LIMIT);
+ Services.prefs.setBoolPref("devtools.webconsole.filter.cssparser", true);
+
+ registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("devtools.hud.loglimit.cssparser");
+ Services.prefs.clearUserPref("devtools.webconsole.filter.cssparser");
+ });
+
+ hud = yield openConsole(tab);
+
+ for (let i = 0; i < 5; i++) {
+ logCSSMessage("css log x");
+ }
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "css log x",
+ category: CATEGORY_CSS,
+ severity: SEVERITY_WARNING,
+ repeats: 5,
+ }],
+ });
+
+ for (let i = 0; i < LOG_LIMIT + 5; i++) {
+ logCSSMessage("css log " + i);
+ }
+
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "css log 5",
+ category: CATEGORY_CSS,
+ severity: SEVERITY_WARNING,
+ },
+ {
+ text: "css log 24", // LOG_LIMIT + 5
+ category: CATEGORY_CSS,
+ severity: SEVERITY_WARNING,
+ }],
+ });
+
+ is(hud.ui.outputNode.querySelectorAll(".message").length, LOG_LIMIT,
+ "number of messages");
+
+ is(Object.keys(hud.ui._repeatNodes).length, LOG_LIMIT,
+ "repeated nodes pruned from repeatNodes");
+
+ let msg = [...result.matched][0];
+ let repeats = msg.querySelector(".message-repeats");
+ is(repeats.getAttribute("value"), 1,
+ "repeated nodes pruned from repeatNodes (confirmed)");
+ }
+
+ function logCSSMessage(msg) {
+ let node = hud.ui.createMessageNode(CATEGORY_CSS, SEVERITY_WARNING, msg);
+ hud.ui.outputMessage(CATEGORY_CSS, node);
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_644419_log_limits.js b/browser/devtools/webconsole/test/browser_webconsole_bug_644419_log_limits.js
new file mode 100644
index 000000000..ac489ee42
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_644419_log_limits.js
@@ -0,0 +1,225 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that the Web Console limits the number of lines displayed according to
+// the limit set for each category.
+
+const INIT_URI = "data:text/html;charset=utf-8,Web Console test for bug 644419: Console should " +
+ "have user-settable log limits for each message category";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/" +
+ "webconsole/test/test-bug-644419-log-limits.html";
+
+let hud, outputNode;
+
+let test = asyncTest(function* () {
+ let { browser } = yield loadTab(INIT_URI);
+
+ hud = yield openConsole();
+
+ hud.jsterm.clearOutput();
+ outputNode = hud.outputNode;
+
+ let loaded = loadBrowser(browser);
+
+ expectUncaughtException();
+
+ content.location = TEST_URI;
+ yield loaded;
+
+ yield testWebDevLimits();
+ yield testWebDevLimits2();
+ yield testJsLimits();
+ yield testJsLimits2();
+
+ yield testNetLimits();
+ yield loadImage();
+ yield testCssLimits();
+ yield testCssLimits2();
+
+ hud = outputNode = null;
+});
+
+function testWebDevLimits() {
+ Services.prefs.setIntPref("devtools.hud.loglimit.console", 10);
+
+ // Find the sentinel entry.
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "bar is not defined",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ }],
+ })
+}
+
+function testWebDevLimits2() {
+ // Fill the log with Web Developer errors.
+ for (let i = 0; i < 11; i++) {
+ content.console.log("test message " + i);
+ }
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "test message 10",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ }).then(() => {
+ testLogEntry(outputNode, "test message 0", "first message is pruned", false, true);
+ findLogEntry("test message 1");
+ // Check if the sentinel entry is still there.
+ findLogEntry("bar is not defined");
+
+ Services.prefs.clearUserPref("devtools.hud.loglimit.console");
+ });
+}
+
+function testJsLimits() {
+ Services.prefs.setIntPref("devtools.hud.loglimit.exception", 10);
+
+ hud.jsterm.clearOutput();
+ content.console.log("testing JS limits");
+
+ // Find the sentinel entry.
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "testing JS limits",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+}
+
+function testJsLimits2() {
+ // Fill the log with JS errors.
+ let head = content.document.getElementsByTagName("head")[0];
+ for (let i = 0; i < 11; i++) {
+ var script = content.document.createElement("script");
+ script.text = "fubar" + i + ".bogus(6);";
+
+ expectUncaughtException();
+ head.insertBefore(script, head.firstChild);
+ }
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "fubar10 is not defined",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ }],
+ }).then(() => {
+ testLogEntry(outputNode, "fubar0 is not defined", "first message is pruned", false, true);
+ findLogEntry("fubar1 is not defined");
+ // Check if the sentinel entry is still there.
+ findLogEntry("testing JS limits");
+
+ Services.prefs.clearUserPref("devtools.hud.loglimit.exception");
+ });
+}
+
+var gCounter, gImage;
+
+function testNetLimits() {
+ Services.prefs.setIntPref("devtools.hud.loglimit.network", 10);
+
+ hud.jsterm.clearOutput();
+ content.console.log("testing Net limits");
+
+ // Find the sentinel entry.
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "testing Net limits",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ }).then(() => {
+ // Fill the log with network messages.
+ gCounter = 0;
+ });
+}
+
+function loadImage() {
+ if (gCounter < 11) {
+ let body = content.document.getElementsByTagName("body")[0];
+ gImage && gImage.removeEventListener("load", loadImage, true);
+ gImage = content.document.createElement("img");
+ gImage.src = "test-image.png?_fubar=" + gCounter;
+ body.insertBefore(gImage, body.firstChild);
+ gImage.addEventListener("load", loadImage, true);
+ gCounter++;
+ return;
+ }
+
+ is(gCounter, 11, "loaded 11 files");
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "test-image.png",
+ url: "test-image.png?_fubar=10",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ }],
+ }).then(() => {
+ let msgs = outputNode.querySelectorAll(".message[category=network]");
+ is(msgs.length, 10, "number of network messages");
+ isnot(msgs[0].url.indexOf("fubar=1"), -1, "first network message");
+ isnot(msgs[1].url.indexOf("fubar=2"), -1, "second network message");
+ findLogEntry("testing Net limits");
+
+ Services.prefs.clearUserPref("devtools.hud.loglimit.network");
+ });
+}
+
+function testCssLimits() {
+ Services.prefs.setIntPref("devtools.hud.loglimit.cssparser", 10);
+
+ hud.jsterm.clearOutput();
+ content.console.log("testing CSS limits");
+
+ // Find the sentinel entry.
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "testing CSS limits",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+}
+
+function testCssLimits2() {
+ // Fill the log with CSS errors.
+ let body = content.document.getElementsByTagName("body")[0];
+ for (let i = 0; i < 11; i++) {
+ var div = content.document.createElement("div");
+ div.setAttribute("style", "-moz-foobar" + i + ": 42;");
+ body.insertBefore(div, body.firstChild);
+ }
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "-moz-foobar10",
+ category: CATEGORY_CSS,
+ severity: SEVERITY_WARNING,
+ }],
+ }).then(() => {
+ testLogEntry(outputNode, "Unknown property '-moz-foobar0'",
+ "first message is pruned", false, true);
+ findLogEntry("Unknown property '-moz-foobar1'");
+ // Check if the sentinel entry is still there.
+ findLogEntry("testing CSS limits");
+
+ Services.prefs.clearUserPref("devtools.hud.loglimit.cssparser");
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_646025_console_file_location.js b/browser/devtools/webconsole/test/browser_webconsole_bug_646025_console_file_location.js
new file mode 100644
index 000000000..39be46e41
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_646025_console_file_location.js
@@ -0,0 +1,54 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that console logging methods display the method location along with
+// the output in the console.
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console file location display test";
+const TEST_URI2 = "http://example.com/browser/browser/devtools/" +
+ "webconsole/test/" +
+ "test-bug-646025-console-file-location.html";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ content.location = TEST_URI2;
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "message for level log",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ source: { url: "test-file-location.js", line: 5 },
+ },
+ {
+ text: "message for level info",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_INFO,
+ source: { url: "test-file-location.js", line: 6 },
+ },
+ {
+ text: "message for level warn",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_WARNING,
+ source: { url: "test-file-location.js", line: 7 },
+ },
+ {
+ text: "message for level error",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_ERROR,
+ source: { url: "test-file-location.js", line: 8 },
+ },
+ {
+ text: "message for level debug",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ source: { url: "test-file-location.js", line: 9 },
+ }],
+ });
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js b/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
new file mode 100644
index 000000000..9f2969214
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_651501_document_body_autocomplete.js
@@ -0,0 +1,109 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that document.body autocompletes in the web console.
+const TEST_URI = "data:text/html;charset=utf-8,Web Console autocompletion bug in document.body";
+
+"use strict";
+
+let gHUD;
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ gHUD = yield openConsole();
+
+ yield consoleOpened();
+ yield autocompletePopupHidden();
+ let view = yield testPropertyPanel();
+ yield onVariablesViewReady(view);
+
+ gHUD = null;
+});
+
+function consoleOpened(aHud) {
+ let deferred = promise.defer();
+
+ let jsterm = gHUD.jsterm;
+ let popup = jsterm.autocompletePopup;
+ let completeNode = jsterm.completeNode;
+
+ ok(!popup.isOpen, "popup is not open");
+
+ popup._panel.addEventListener("popupshown", function onShown() {
+ popup._panel.removeEventListener("popupshown", onShown, false);
+
+ ok(popup.isOpen, "popup is open");
+
+ is(popup.itemCount, jsterm._autocompleteCache.length,
+ "popup.itemCount is correct");
+ isnot(jsterm._autocompleteCache.indexOf("addEventListener"), -1,
+ "addEventListener is in the list of suggestions");
+ isnot(jsterm._autocompleteCache.indexOf("bgColor"), -1,
+ "bgColor is in the list of suggestions");
+ isnot(jsterm._autocompleteCache.indexOf("ATTRIBUTE_NODE"), -1,
+ "ATTRIBUTE_NODE is in the list of suggestions");
+
+ popup._panel.addEventListener("popuphidden", deferred.resolve, false);
+
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ }, false);
+
+ jsterm.setInputValue("document.body");
+ EventUtils.synthesizeKey(".", {});
+
+ return deferred.promise;
+}
+
+function autocompletePopupHidden()
+{
+ let deferred = promise.defer();
+
+ let jsterm = gHUD.jsterm;
+ let popup = jsterm.autocompletePopup;
+ let completeNode = jsterm.completeNode;
+ let inputNode = jsterm.inputNode;
+
+ popup._panel.removeEventListener("popuphidden", autocompletePopupHidden, false);
+
+ ok(!popup.isOpen, "popup is not open");
+
+ jsterm.once("autocomplete-updated", function() {
+ is(completeNode.value, testStr + "dy", "autocomplete shows document.body");
+ deferred.resolve();
+ });
+
+ let inputStr = "document.b";
+ jsterm.setInputValue(inputStr);
+ EventUtils.synthesizeKey("o", {});
+ let testStr = inputStr.replace(/./g, " ") + " ";
+
+ return deferred.promise;
+}
+
+function testPropertyPanel()
+{
+ let deferred = promise.defer();
+
+ let jsterm = gHUD.jsterm;
+ jsterm.clearOutput();
+ jsterm.execute("document", (msg) => {
+ jsterm.once("variablesview-fetched", (aEvent, aView) => {
+ deferred.resolve(aView);
+ });
+ let anchor = msg.querySelector(".message-body a");
+ EventUtils.synthesizeMouse(anchor, 2, 2, {}, gHUD.iframeWindow);
+ });
+
+ return deferred.promise;
+}
+
+function onVariablesViewReady(aView)
+{
+ return findVariableViewProperties(aView, [
+ { name: "body", value: "<body>" },
+ ], { webconsole: gHUD });
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js b/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
new file mode 100644
index 000000000..a5191c831
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
@@ -0,0 +1,107 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+// Tests that the $0 console helper works as intended.
+
+let inspector, h1, outputNode;
+
+function createDocument() {
+ let doc = content.document;
+ let div = doc.createElement("div");
+ h1 = doc.createElement("h1");
+ let p1 = doc.createElement("p");
+ let p2 = doc.createElement("p");
+ let div2 = doc.createElement("div");
+ let p3 = doc.createElement("p");
+ doc.title = "Inspector Tree Selection Test";
+ h1.textContent = "Inspector Tree Selection Test";
+ p1.textContent = "This is some example text";
+ p2.textContent = "Lorem ipsum dolor sit amet, consectetur adipisicing " +
+ "elit, sed do eiusmod tempor incididunt ut labore et dolore magna " +
+ "aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
+ "laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
+ "dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
+ "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " +
+ "proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
+ p3.textContent = "Lorem ipsum dolor sit amet, consectetur adipisicing " +
+ "elit, sed do eiusmod tempor incididunt ut labore et dolore magna " +
+ "aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco " +
+ "laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure " +
+ "dolor in reprehenderit in voluptate velit esse cillum dolore eu " +
+ "fugiat nulla pariatur. Excepteur sint occaecat cupidatat non " +
+ "proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
+ div.appendChild(h1);
+ div.appendChild(p1);
+ div.appendChild(p2);
+ div2.appendChild(p3);
+ doc.body.appendChild(div);
+ doc.body.appendChild(div2);
+ setupHighlighterTests();
+}
+
+function setupHighlighterTests() {
+ ok(h1, "we have the header node");
+ openInspector().then(runSelectionTests);
+}
+
+let runSelectionTests = Task.async(function*(aInspector) {
+ inspector = aInspector;
+
+ let onPickerStarted = inspector.toolbox.once("picker-started");
+ inspector.toolbox.highlighterUtils.startPicker();
+ yield onPickerStarted;
+
+ info("Picker mode started, now clicking on H1 to select that node");
+ h1.scrollIntoView();
+ let onPickerStopped = inspector.toolbox.once("picker-stopped");
+ let onInspectorUpdated = inspector.once("inspector-updated");
+ EventUtils.synthesizeMouseAtCenter(h1, {}, content);
+ yield onPickerStopped;
+ yield onInspectorUpdated;
+
+ info("Picker mode stopped, H1 selected, now switching to the console");
+ let hud = yield openConsole(gBrowser.selectedTab);
+
+ performWebConsoleTests(hud);
+});
+
+function performWebConsoleTests(hud) {
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ let jsterm = hud.jsterm;
+ outputNode = hud.outputNode;
+
+ jsterm.clearOutput();
+ jsterm.execute("$0", onNodeOutput);
+
+ function onNodeOutput(node) {
+ isnot(node.textContent.indexOf("<h1>"), -1, "correct output for $0");
+
+ jsterm.clearOutput();
+ jsterm.execute("$0.textContent = 'bug653531'", onNodeUpdate);
+ }
+
+ function onNodeUpdate(node) {
+ isnot(node.textContent.indexOf("bug653531"), -1,
+ "correct output for $0.textContent");
+ is(inspector.selection.node.textContent, "bug653531",
+ "node successfully updated");
+
+ inspector = h1 = outputNode = null;
+ gBrowser.removeCurrentTab();
+ finishTest();
+ }
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+ gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+ waitForFocus(createDocument, content);
+ }, true);
+
+ content.location = "data:text/html;charset=utf-8,test for highlighter helper in web console";
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_658368_time_methods.js b/browser/devtools/webconsole/test/browser_webconsole_bug_658368_time_methods.js
new file mode 100644
index 000000000..8cb87324e
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_658368_time_methods.js
@@ -0,0 +1,66 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that the Console API implements the time() and timeEnd() methods.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/" +
+ "test/test-bug-658368-time-methods.html";
+
+const TEST_URI2 = "data:text/html;charset=utf-8,<script>" +
+ "console.timeEnd('bTimer');</script>";
+
+const TEST_URI3 = "data:text/html;charset=utf-8,<script>" +
+ "console.time('bTimer');</script>";
+
+const TEST_URI4 = "data:text/html;charset=utf-8," +
+ "<script>console.timeEnd('bTimer');</script>";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud1 = yield openConsole();
+
+ yield waitForMessages({
+ webconsole: hud1,
+ messages: [{
+ name: "aTimer started",
+ consoleTime: "aTimer",
+ }, {
+ name: "aTimer end",
+ consoleTimeEnd: "aTimer",
+ }],
+ });
+
+ // The next test makes sure that timers with the same name but in separate
+ // tabs, do not contain the same value.
+ let { browser } = yield loadTab(TEST_URI2);
+ let hud2 = yield openConsole();
+
+ testLogEntry(hud2.outputNode, "bTimer: timer started",
+ "bTimer was not started", false, true);
+
+ // The next test makes sure that timers with the same name but in separate
+ // pages, do not contain the same value.
+ content.location = TEST_URI3;
+
+ yield waitForMessages({
+ webconsole: hud2,
+ messages: [{
+ name: "bTimer started",
+ consoleTime: "bTimer",
+ }],
+ });
+
+ hud2.jsterm.clearOutput();
+
+ // Now the following console.timeEnd() call shouldn't display anything,
+ // if the timers in different pages are not related.
+ content.location = TEST_URI4;
+ yield loadBrowser(browser);
+
+ testLogEntry(hud2.outputNode, "bTimer: timer started",
+ "bTimer was not started", false, true);
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_659907_console_dir.js b/browser/devtools/webconsole/test/browser_webconsole_bug_659907_console_dir.js
new file mode 100644
index 000000000..1569899a1
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_659907_console_dir.js
@@ -0,0 +1,28 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that console.dir works as intended.
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 659907: " +
+ "Expand console object with a dir method"
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ hud.jsterm.clearOutput();
+
+ hud.jsterm.execute("console.dir(document)");
+
+ let varView = yield hud.jsterm.once("variablesview-fetched");
+
+ yield findVariableViewProperties(varView, [
+ { name: "__proto__.__proto__.querySelectorAll", value: "querySelectorAll()" },
+ { name: "location", value: /Location \u2192 data:Web/ },
+ { name: "__proto__.write", value: "write()" },
+ ], { webconsole: hud });
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_660806_history_nav.js b/browser/devtools/webconsole/test/browser_webconsole_bug_660806_history_nav.js
new file mode 100644
index 000000000..2f057e4ca
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_660806_history_nav.js
@@ -0,0 +1,51 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>bug 660806 - history navigation must not show the autocomplete popup";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ yield consoleOpened(hud);
+});
+
+function consoleOpened(HUD)
+{
+ let deferred = promise.defer();
+
+ let jsterm = HUD.jsterm;
+ let popup = jsterm.autocompletePopup;
+ let onShown = function() {
+ ok(false, "popup shown");
+ };
+
+ jsterm.execute("window.foobarBug660806 = {\
+ 'location': 'value0',\
+ 'locationbar': 'value1'\
+ }");
+
+ popup._panel.addEventListener("popupshown", onShown, false);
+
+ ok(!popup.isOpen, "popup is not open");
+
+ ok(!jsterm.lastInputValue, "no lastInputValue");
+ jsterm.setInputValue("window.foobarBug660806.location");
+ is(jsterm.lastInputValue, "window.foobarBug660806.location",
+ "lastInputValue is correct");
+
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ EventUtils.synthesizeKey("VK_UP", {});
+
+ is(jsterm.lastInputValue, "window.foobarBug660806.location",
+ "lastInputValue is correct, again");
+
+ executeSoon(function() {
+ ok(!popup.isOpen, "popup is not open");
+ popup._panel.removeEventListener("popupshown", onShown, false);
+ executeSoon(deferred.resolve);
+ });
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_664131_console_group.js b/browser/devtools/webconsole/test/browser_webconsole_bug_664131_console_group.js
new file mode 100644
index 000000000..ff719877c
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_664131_console_group.js
@@ -0,0 +1,77 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that console.group/groupEnd works as intended.
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 664131: Expand console object with group methods";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+ let jsterm = hud.jsterm;
+ let outputNode = hud.outputNode;
+
+ hud.jsterm.clearOutput();
+
+ yield jsterm.execute("console.group('bug664131a')")
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "bug664131a",
+ consoleGroup: 1,
+ }],
+ });
+
+ yield jsterm.execute("console.log('bug664131a-inside')")
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "bug664131a-inside",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ groupDepth: 1,
+ }],
+ });
+
+ yield jsterm.execute('console.groupEnd("bug664131a")');
+ yield jsterm.execute('console.log("bug664131-outside")');
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "bug664131-outside",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ groupDepth: 0,
+ }],
+ });
+
+ yield jsterm.execute('console.groupCollapsed("bug664131b")');
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "bug664131b",
+ consoleGroup: 1,
+ }],
+ });
+
+ // Test that clearing the console removes the indentation.
+ hud.jsterm.clearOutput();
+ yield jsterm.execute('console.log("bug664131-cleared")');
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "bug664131-cleared",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ groupDepth: 0,
+ }],
+ });
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_686937_autocomplete_JSTerm_helpers.js b/browser/devtools/webconsole/test/browser_webconsole_bug_686937_autocomplete_JSTerm_helpers.js
new file mode 100644
index 000000000..d2e45489d
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_686937_autocomplete_JSTerm_helpers.js
@@ -0,0 +1,60 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that the autocompletion results contain the names of JSTerm helpers.
+
+const TEST_URI = "data:text/html;charset=utf8,<p>test JSTerm Helpers autocomplete";
+
+let jsterm;
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ jsterm = hud.jsterm;
+ let input = jsterm.inputNode;
+ let popup = jsterm.autocompletePopup;
+
+ // Test if 'i' gives 'inspect'
+ input.value = "i";
+ input.setSelectionRange(1, 1);
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ let newItems = popup.getItems().map(function(e) {return e.label;});
+ ok(newItems.indexOf("inspect") > -1, "autocomplete results contain helper 'inspect'");
+
+ // Test if 'window.' does not give 'inspect'.
+ input.value = "window.";
+ input.setSelectionRange(7, 7);
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ newItems = popup.getItems().map(function(e) {return e.label;});
+ is(newItems.indexOf("inspect"), -1, "autocomplete results do not contain helper 'inspect'");
+
+ // Test if 'dump(i' gives 'inspect'
+ input.value = "dump(i";
+ input.setSelectionRange(6, 6);
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ newItems = popup.getItems().map(function(e) {return e.label;});
+ ok(newItems.indexOf("inspect") > -1, "autocomplete results contain helper 'inspect'");
+
+ // Test if 'window.dump(i' gives 'inspect'
+ input.value = "window.dump(i";
+ input.setSelectionRange(13, 13);
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ newItems = popup.getItems().map(function(e) {return e.label;});
+ ok(newItems.indexOf("inspect") > -1, "autocomplete results contain helper 'inspect'");
+
+ jsterm = null;
+});
+
+function complete(type) {
+ let updated = jsterm.once("autocomplete-updated");
+ jsterm.complete(type);
+ return updated;
+} \ No newline at end of file
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_704295.js b/browser/devtools/webconsole/test/browser_webconsole_bug_704295.js
new file mode 100644
index 000000000..14d4aeeab
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_704295.js
@@ -0,0 +1,39 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests for bug 704295
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ testCompletion(hud);
+});
+
+function testCompletion(hud) {
+ var jsterm = hud.jsterm;
+ var input = jsterm.inputNode;
+
+ // Test typing 'var d = 5;' and press RETURN
+ jsterm.setInputValue("var d = ");
+ EventUtils.synthesizeKey("5", {});
+ EventUtils.synthesizeKey(";", {});
+ is(input.value, "var d = 5;", "var d = 5;");
+ is(jsterm.completeNode.value, "", "no completion");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ is(jsterm.completeNode.value, "", "clear completion on execute()");
+
+ // Test typing 'var a = d' and press RETURN
+ jsterm.setInputValue("var a = ");
+ EventUtils.synthesizeKey("d", {});
+ is(input.value, "var a = d", "var a = d");
+ is(jsterm.completeNode.value, "", "no completion");
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ is(jsterm.completeNode.value, "", "clear completion on execute()");
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_734061_No_input_change_and_Tab_key_pressed.js b/browser/devtools/webconsole/test/browser_webconsole_bug_734061_No_input_change_and_Tab_key_pressed.js
new file mode 100644
index 000000000..9aff77a44
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_734061_No_input_change_and_Tab_key_pressed.js
@@ -0,0 +1,32 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/browser/test-console.html";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ var jsterm = hud.jsterm;
+ var input = jsterm.inputNode;
+
+ is(input.getAttribute("focused"), "true", "input has focus");
+ EventUtils.synthesizeKey("VK_TAB", {});
+ is(input.getAttribute("focused"), "", "focus moved away");
+
+ // Test user changed something
+ input.focus();
+ EventUtils.synthesizeKey("A", {});
+ EventUtils.synthesizeKey("VK_TAB", {});
+ is(input.getAttribute("focused"), "true", "input is still focused");
+
+ // Test non empty input but not changed since last focus
+ input.blur();
+ input.focus();
+ EventUtils.synthesizeKey("VK_RIGHT", {});
+ EventUtils.synthesizeKey("VK_TAB", {});
+ is(input.getAttribute("focused"), "", "input moved away");
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_737873_mixedcontent.js b/browser/devtools/webconsole/test/browser_webconsole_bug_737873_mixedcontent.js
new file mode 100644
index 000000000..bc2f70b37
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_737873_mixedcontent.js
@@ -0,0 +1,60 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Tanvi Vyas <tanvi@mozilla.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Tests that the Web Console Mixed Content messages are displayed
+
+const TEST_URI = "data:text/html;charset=utf8,Web Console mixed content test";
+const TEST_HTTPS_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html";
+const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/MixedContent";
+
+let test = asyncTest(function* () {
+ Services.prefs.setBoolPref("security.mixed_content.block_display_content", false);
+ Services.prefs.setBoolPref("security.mixed_content.block_active_content", false);
+
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ yield testMixedContent(hud);
+
+ Services.prefs.clearUserPref("security.mixed_content.block_display_content");
+ Services.prefs.clearUserPref("security.mixed_content.block_active_content");
+});
+
+let testMixedContent = Task.async(function*(hud) {
+ content.location = TEST_HTTPS_URI;
+
+ let results = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "example.com",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_WARNING,
+ }],
+ });
+
+ let msg = [...results[0].matched][0];
+ ok(msg, "page load logged");
+ ok(msg.classList.contains("mixed-content"), ".mixed-content element");
+
+ let link = msg.querySelector(".learn-more-link");
+ ok(link, "mixed content link element");
+ is(link.textContent, "[Mixed Content]", "link text is accurate");
+
+ yield simulateMessageLinkClick(link, LEARN_MORE_URI);
+
+ ok(!msg.classList.contains("filtered-by-type"), "message is not filtered");
+
+ hud.setFilterState("netwarn", false);
+
+ ok(msg.classList.contains("filtered-by-type"), "message is filtered");
+
+ hud.setFilterState("netwarn", true);
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_752559_ineffective_iframe_sandbox_warning.js b/browser/devtools/webconsole/test/browser_webconsole_bug_752559_ineffective_iframe_sandbox_warning.js
new file mode 100644
index 000000000..fac6f2cc2
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_752559_ineffective_iframe_sandbox_warning.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that warnings about ineffective iframe sandboxing are logged to the
+// web console when necessary (and not otherwise).
+
+const TEST_URI_WARNING = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning0.html";
+const TEST_URI_NOWARNING = [
+ "http://example.com/browser/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning1.html",
+ "http://example.com/browser/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning2.html",
+ "http://example.com/browser/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning3.html",
+ "http://example.com/browser/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning4.html",
+ "http://example.com/browser/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning5.html"
+];
+
+const INEFFECTIVE_IFRAME_SANDBOXING_MSG = "An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can remove its sandboxing.";
+const SENTINEL_MSG = "testing ineffective sandboxing message";
+
+function test()
+{
+ loadTab(TEST_URI_WARNING).then(() => {
+ openConsole().then((hud) => {
+ content.console.log(SENTINEL_MSG)
+ waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "Ineffective iframe sandboxing warning displayed successfully",
+ text: INEFFECTIVE_IFRAME_SANDBOXING_MSG,
+ category: CATEGORY_SECURITY,
+ severity: SEVERITY_WARNING
+ },
+ {
+ text: SENTINEL_MSG,
+ severity: SEVERITY_LOG
+ }
+ ]
+ }).then(() => {
+ let msgs = hud.outputNode.querySelectorAll(".message[category=security]");
+ is(msgs.length, 1, "one security message");
+ testNoWarning(0);
+ });
+ })
+ });
+}
+
+function testNoWarning(id)
+{
+ loadTab(TEST_URI_NOWARNING[id]).then(() => {
+ openConsole().then((hud) => {
+ content.console.log(SENTINEL_MSG)
+ waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ text: SENTINEL_MSG,
+ severity: SEVERITY_LOG
+ }
+ ]
+ }).then(() => {
+ let msgs = hud.outputNode.querySelectorAll(".message[category=security]");
+ is(msgs.length, 0, "no security messages (case " + id + ")");
+
+ id += 1;
+ if (id < TEST_URI_NOWARNING.length) {
+ testNoWarning(id);
+ } else {
+ finishTest();
+ }
+ });
+ })
+ });
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_762593_insecure_passwords_about_blank_web_console_warning.js b/browser/devtools/webconsole/test/browser_webconsole_bug_762593_insecure_passwords_about_blank_web_console_warning.js
new file mode 100644
index 000000000..d0322f6e9
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_762593_insecure_passwords_about_blank_web_console_warning.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/*
+ * Tests that errors about insecure passwords are logged
+ * to the web console
+ */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-762593-insecure-passwords-about-blank-web-console-warning.html";
+const INSECURE_PASSWORD_MSG = "Password fields present on an insecure (http://) page. This is a security risk that allows user login credentials to be stolen.";
+
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "Insecure password error displayed successfully",
+ text: INSECURE_PASSWORD_MSG,
+ category: CATEGORY_SECURITY,
+ severity: SEVERITY_WARNING
+ },
+ ],
+ });
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_762593_insecure_passwords_web_console_warning.js b/browser/devtools/webconsole/test/browser_webconsole_bug_762593_insecure_passwords_web_console_warning.js
new file mode 100644
index 000000000..428add7d2
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_762593_insecure_passwords_web_console_warning.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/*
+ * Tests that errors about insecure passwords are logged
+ * to the web console
+ */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-762593-insecure-passwords-web-console-warning.html";
+const INSECURE_PASSWORD_MSG = "Password fields present on an insecure (http://) page. This is a security risk that allows user login credentials to be stolen.";
+const INSECURE_FORM_ACTION_MSG = "Password fields present in a form with an insecure (http://) form action. This is a security risk that allows user login credentials to be stolen.";
+const INSECURE_IFRAME_MSG = "Password fields present on an insecure (http://) iframe. This is a security risk that allows user login credentials to be stolen.";
+const INSECURE_PASSWORDS_URI = "https://developer.mozilla.org/docs/Security/InsecurePasswords";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ let result = yield waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "Insecure password error displayed successfully",
+ text: INSECURE_PASSWORD_MSG,
+ category: CATEGORY_SECURITY,
+ severity: SEVERITY_WARNING
+ },
+ {
+ name: "Insecure iframe error displayed successfully",
+ text: INSECURE_IFRAME_MSG,
+ category: CATEGORY_SECURITY,
+ severity: SEVERITY_WARNING
+ },
+ {
+ name: "Insecure form action error displayed successfully",
+ text: INSECURE_FORM_ACTION_MSG,
+ category: CATEGORY_SECURITY,
+ severity: SEVERITY_WARNING
+ },
+ ],
+ });
+
+ yield testClickOpenNewTab(hud, result);
+});
+
+function testClickOpenNewTab(hud, [result]) {
+ let msg = [...result.matched][0];
+ let warningNode = msg.querySelector(".learn-more-link");
+ ok(warningNode, "learn more link");
+ return simulateMessageLinkClick(warningNode, INSECURE_PASSWORDS_URI);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_764572_output_open_url.js b/browser/devtools/webconsole/test/browser_webconsole_bug_764572_output_open_url.js
new file mode 100644
index 000000000..2517c8e94
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_764572_output_open_url.js
@@ -0,0 +1,137 @@
+/* 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/. */
+
+// This is a test for the Open URL context menu item
+// that is shown for network requests
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html"
+const COMMAND_NAME = "consoleCmd_openURL";
+const CONTEXT_MENU_ID = "#menu_openURL";
+
+let HUD = null, outputNode = null, contextMenu = null;
+
+let test = asyncTest(function* () {
+ Services.prefs.setBoolPref("devtools.webconsole.filter.networkinfo", true);
+
+ yield loadTab(TEST_URI);
+ HUD = yield openConsole();
+
+ let results = yield consoleOpened();
+ yield onConsoleMessage(results);
+
+ let results2 = yield testOnNetActivity();
+ let msg = yield onNetworkMessage(results2);
+
+ yield testOnNetActivity_contextmenu(msg);
+
+ Services.prefs.clearUserPref("devtools.webconsole.filter.networkinfo");
+
+ HUD = null, outputNode = null, contextMenu = null;
+});
+
+function consoleOpened() {
+ outputNode = HUD.outputNode;
+ contextMenu = HUD.iframeWindow.document.getElementById("output-contextmenu");
+
+ HUD.jsterm.clearOutput();
+
+ content.console.log("bug 764572");
+
+ return waitForMessages({
+ webconsole: HUD,
+ messages: [{
+ text: "bug 764572",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+}
+
+function onConsoleMessage(aResults) {
+ outputNode.focus();
+ outputNode.selectedItem = [...aResults[0].matched][0];
+
+ // Check if the command is disabled non-network messages.
+ goUpdateCommand(COMMAND_NAME);
+ let controller = top.document.commandDispatcher
+ .getControllerForCommand(COMMAND_NAME);
+
+ let isDisabled = !controller || !controller.isCommandEnabled(COMMAND_NAME);
+ ok(isDisabled, COMMAND_NAME + " should be disabled.");
+
+ outputNode.selectedItem.scrollIntoView();
+ return waitForContextMenu(contextMenu, outputNode.selectedItem, () => {
+ let isHidden = contextMenu.querySelector(CONTEXT_MENU_ID).hidden;
+ ok(isHidden, CONTEXT_MENU_ID + " should be hidden.");
+ });
+}
+
+function testOnNetActivity() {
+ HUD.jsterm.clearOutput();
+
+ // Reload the url to show net activity in console.
+ content.location.reload();
+
+ return waitForMessages({
+ webconsole: HUD,
+ messages: [{
+ text: "test-console.html",
+ category: CATEGORY_NETWORK,
+ severity: SEVERITY_LOG,
+ }],
+ });
+}
+
+function onNetworkMessage(aResults) {
+ let deferred = promise.defer();
+
+ outputNode.focus();
+ let msg = [...aResults[0].matched][0];
+ ok(msg, "network message");
+ HUD.ui.output.selectMessage(msg);
+
+ let currentTab = gBrowser.selectedTab;
+ let newTab = null;
+
+ gBrowser.tabContainer.addEventListener("TabOpen", function onOpen(aEvent) {
+ gBrowser.tabContainer.removeEventListener("TabOpen", onOpen, true);
+ newTab = aEvent.target;
+ newTab.linkedBrowser.addEventListener("load", onTabLoaded, true);
+ }, true);
+
+ function onTabLoaded() {
+ newTab.linkedBrowser.removeEventListener("load", onTabLoaded, true);
+ gBrowser.removeTab(newTab);
+ gBrowser.selectedTab = currentTab;
+ executeSoon(deferred.resolve.bind(null, msg));
+ }
+
+ // Check if the command is enabled for a network message.
+ goUpdateCommand(COMMAND_NAME);
+ let controller = top.document.commandDispatcher
+ .getControllerForCommand(COMMAND_NAME);
+ ok(controller.isCommandEnabled(COMMAND_NAME),
+ COMMAND_NAME + " should be enabled.");
+
+ // Try to open the URL.
+ goDoCommand(COMMAND_NAME);
+
+ return deferred.promise;
+}
+
+function testOnNetActivity_contextmenu(msg) {
+ let deferred = promise.defer();
+
+ outputNode.focus();
+ HUD.ui.output.selectMessage(msg);
+ msg.scrollIntoView();
+
+ info("net activity context menu");
+
+ waitForContextMenu(contextMenu, msg, () => {
+ let isShown = !contextMenu.querySelector(CONTEXT_MENU_ID).hidden;
+ ok(isShown, CONTEXT_MENU_ID + " should be shown.");
+ }).then(deferred.resolve);
+
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js b/browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js
new file mode 100644
index 000000000..05580a7c3
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js
@@ -0,0 +1,78 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that message source links for js errors and console API calls open in
+// the jsdebugger when clicked.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test" +
+ "/test-bug-766001-js-console-links.html";
+
+function test() {
+ let hud;
+
+ requestLongerTimeout(2);
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ expectUncaughtException();
+
+ let {tab} = yield loadTab(TEST_URI);
+ hud = yield openConsole(tab);
+
+ let [exceptionRule, consoleRule] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "document.bar",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ },
+ {
+ text: "Blah Blah",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ let exceptionMsg = [...exceptionRule.matched][0];
+ let consoleMsg = [...consoleRule.matched][0];
+ let nodes = [exceptionMsg.querySelector(".message-location"),
+ consoleMsg.querySelector(".message-location")];
+ ok(nodes[0], ".location node for the exception message");
+ ok(nodes[1], ".location node for the console message");
+
+ for (let i = 0; i < nodes.length; i++) {
+ yield checkClickOnNode(i, nodes[i]);
+ yield gDevTools.showToolbox(hud.target, "webconsole");
+ }
+
+ // check again the first node.
+ yield checkClickOnNode(0, nodes[0]);
+ }
+
+ function* checkClickOnNode(index, node) {
+ info("checking click on node index " + index);
+
+ let url = node.getAttribute("title");
+ ok(url, "source url found for index " + index);
+
+ let line = node.sourceLine;
+ ok(line, "found source line for index " + index);
+
+ executeSoon(() => {
+ EventUtils.sendMouseEvent({ type: "click" }, node);
+ });
+
+ yield hud.ui.once("source-in-debugger-opened");
+
+ let toolbox = yield gDevTools.getToolbox(hud.target);
+ let {panelWin: { DebuggerView: view }} = toolbox.getPanel("jsdebugger");
+ is(view.Sources.selectedValue,
+ getSourceActor(view.Sources, url),
+ "expected source url");
+ is(view.editor.getCursor().line, line - 1, "expected source line");
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_770099_violation.js b/browser/devtools/webconsole/test/browser_webconsole_bug_770099_violation.js
new file mode 100644
index 000000000..6c0400492
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_770099_violation.js
@@ -0,0 +1,31 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Tests that the Web Console CSP messages are displayed
+
+const TEST_URI = "data:text/html;charset=utf8,Web Console CSP violation test";
+const TEST_VIOLATION = "https://example.com/browser/browser/devtools/webconsole/test/test_bug_770099_violation.html";
+const CSP_VIOLATION_MSG = 'Content Security Policy: The page\'s settings blocked the loading of a resource at http://some.example.com/test.png ("default-src https://example.com").'
+
+let test = asyncTest(function* () {
+ let { browser } = yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ hud.jsterm.clearOutput();
+
+ let loaded = loadBrowser(browser);
+ content.location = TEST_VIOLATION;
+ yield loaded;
+
+ yield waitForSuccess({
+ name: "CSP policy URI warning displayed successfully",
+ validator: function() {
+ return hud.outputNode.textContent.indexOf(CSP_VIOLATION_MSG) > -1;
+ }
+ });
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js b/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
new file mode 100644
index 000000000..6cc5172c5
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
@@ -0,0 +1,147 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ * ***** END LICENSE BLOCK ***** */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test" +
+ "/test-bug-782653-css-errors.html";
+
+let nodes, hud, StyleEditorUI;
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ hud = yield openConsole();
+
+ let styleEditor = yield testViewSource();
+ yield onStyleEditorReady(styleEditor);
+
+ nodes = hud = StyleEditorUI = null;
+});
+
+function testViewSource()
+{
+ let deferred = promise.defer();
+
+ waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "'font-weight'",
+ category: CATEGORY_CSS,
+ severity: SEVERITY_WARNING,
+ },
+ {
+ text: "'color'",
+ category: CATEGORY_CSS,
+ severity: SEVERITY_WARNING,
+ }],
+ }).then(([error1Rule, error2Rule]) => {
+ let error1Msg = [...error1Rule.matched][0];
+ let error2Msg = [...error2Rule.matched][0];
+ nodes = [error1Msg.querySelector(".message-location"),
+ error2Msg.querySelector(".message-location")];
+ ok(nodes[0], ".message-location node for the first error");
+ ok(nodes[1], ".message-location node for the second error");
+
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ let toolbox = gDevTools.getToolbox(target);
+ toolbox.once("styleeditor-selected", (event, panel) => {
+ StyleEditorUI = panel.UI;
+
+ let count = 0;
+ StyleEditorUI.on("editor-added", function() {
+ if (++count == 2) {
+ deferred.resolve(panel);
+ }
+ });
+ });
+
+ EventUtils.sendMouseEvent({ type: "click" }, nodes[0]);
+ });
+
+ return deferred.promise;
+}
+
+function onStyleEditorReady(aPanel)
+{
+ let deferred = promise.defer();
+
+ let win = aPanel.panelWindow;
+ ok(win, "Style Editor Window is defined");
+ ok(StyleEditorUI, "Style Editor UI is defined");
+
+ waitForFocus(function() {
+ info("style editor window focused");
+
+ let href = nodes[0].getAttribute("title");
+ let line = nodes[0].sourceLine;
+ ok(line, "found source line");
+
+ checkStyleEditorForSheetAndLine(href, line - 1).then(function() {
+ info("first check done");
+
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ let toolbox = gDevTools.getToolbox(target);
+
+ let href = nodes[1].getAttribute("title");
+ let line = nodes[1].sourceLine;
+ ok(line, "found source line");
+
+ toolbox.selectTool("webconsole").then(function() {
+ info("webconsole selected");
+
+ toolbox.once("styleeditor-selected", function(aEvent) {
+ info(aEvent + " event fired");
+
+ checkStyleEditorForSheetAndLine(href, line - 1).then(deferred.resolve);
+ });
+
+ EventUtils.sendMouseEvent({ type: "click" }, nodes[1]);
+ });
+ });
+ }, win);
+
+ return deferred.promise;
+}
+
+function checkStyleEditorForSheetAndLine(aHref, aLine)
+{
+ let foundEditor = null;
+ for (let editor of StyleEditorUI.editors) {
+ if (editor.styleSheet.href == aHref) {
+ foundEditor = editor;
+ break;
+ }
+ }
+
+ ok(foundEditor, "found style editor for " + aHref);
+ return performLineCheck(foundEditor, aLine);
+}
+
+function performLineCheck(aEditor, aLine)
+{
+ let deferred = promise.defer();
+
+ function checkForCorrectState()
+ {
+ is(aEditor.sourceEditor.getCursor().line, aLine,
+ "correct line is selected");
+ is(StyleEditorUI.selectedStyleSheetIndex, aEditor.styleSheet.styleSheetIndex,
+ "correct stylesheet is selected in the editor");
+
+ executeSoon(deferred.resolve);
+ }
+
+ info("wait for source editor to load");
+
+ // Get out of the styleeditor-selected event loop.
+ executeSoon(() => {
+ aEditor.getSourceEditor().then(() => {
+ // Get out of the editor's source-editor-load event loop.
+ executeSoon(checkForCorrectState);
+ });
+ });
+
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_804845_ctrl_key_nav.js b/browser/devtools/webconsole/test/browser_webconsole_bug_804845_ctrl_key_nav.js
new file mode 100644
index 000000000..f6a66572f
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_804845_ctrl_key_nav.js
@@ -0,0 +1,217 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * zmgmoz <zmgmoz@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Test navigation of webconsole contents via ctrl-a, ctrl-e, ctrl-p, ctrl-n
+// see https://bugzilla.mozilla.org/show_bug.cgi?id=804845
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 804845 and bug 619598";
+
+let jsterm, inputNode;
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ doTests(hud);
+
+ jsterm = inputNode = null;
+});
+
+function doTests(HUD) {
+ jsterm = HUD.jsterm;
+ inputNode = jsterm.inputNode;
+ ok(!jsterm.inputNode.value, "inputNode.value is empty");
+ is(jsterm.inputNode.selectionStart, 0);
+ is(jsterm.inputNode.selectionEnd, 0);
+
+ testSingleLineInputNavNoHistory();
+ testMultiLineInputNavNoHistory();
+ testNavWithHistory();
+}
+
+function testSingleLineInputNavNoHistory() {
+ // Single char input
+ EventUtils.synthesizeKey("1", {});
+ is(inputNode.selectionStart, 1, "caret location after single char input");
+
+ // nav to start/end with ctrl-a and ctrl-e;
+ EventUtils.synthesizeKey("a", { ctrlKey: true });
+ is(inputNode.selectionStart, 0, "caret location after single char input and ctrl-a");
+
+ EventUtils.synthesizeKey("e", { ctrlKey: true });
+ is(inputNode.selectionStart, 1, "caret location after single char input and ctrl-e");
+
+ // Second char input
+ EventUtils.synthesizeKey("2", {});
+ // nav to start/end with up/down keys; verify behaviour using ctrl-p/ctrl-n
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(inputNode.selectionStart, 0, "caret location after two char input and VK_UP");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(inputNode.selectionStart, 2, "caret location after two char input and VK_DOWN");
+
+ EventUtils.synthesizeKey("a", { ctrlKey: true });
+ is(inputNode.selectionStart, 0, "move caret to beginning of 2 char input with ctrl-a");
+ EventUtils.synthesizeKey("a", { ctrlKey: true });
+ is(inputNode.selectionStart, 0, "no change of caret location on repeat ctrl-a");
+ EventUtils.synthesizeKey("p", { ctrlKey: true });
+ is(inputNode.selectionStart, 0, "no change of caret location on ctrl-p from beginning of line");
+
+ EventUtils.synthesizeKey("e", { ctrlKey: true });
+ is(inputNode.selectionStart, 2, "move caret to end of 2 char input with ctrl-e");
+ EventUtils.synthesizeKey("e", { ctrlKey: true });
+ is(inputNode.selectionStart, 2, "no change of caret location on repeat ctrl-e");
+ EventUtils.synthesizeKey("n", { ctrlKey: true });
+ is(inputNode.selectionStart, 2, "no change of caret location on ctrl-n from end of line");
+
+ EventUtils.synthesizeKey("p", { ctrlKey: true });
+ is(inputNode.selectionStart, 0, "ctrl-p moves to start of line");
+
+ EventUtils.synthesizeKey("n", { ctrlKey: true });
+ is(inputNode.selectionStart, 2, "ctrl-n moves to end of line");
+}
+
+function testMultiLineInputNavNoHistory() {
+ let lineValues = ["one", "2", "something longer", "", "", "three!"];
+ jsterm.setInputValue("");
+ // simulate shift-return
+ for (let i = 0; i < lineValues.length; i++) {
+ jsterm.setInputValue(inputNode.value + lineValues[i]);
+ EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true });
+ }
+ let inputValue = inputNode.value;
+ is(inputNode.selectionStart, inputNode.selectionEnd);
+ is(inputNode.selectionStart, inputValue.length, "caret at end of multiline input");
+
+ // possibility newline is represented by one ('\r', '\n') or two ('\r\n') chars
+ let newlineString = inputValue.match(/(\r\n?|\n\r?)$/)[0];
+
+ // Ok, test navigating within the multi-line string!
+ EventUtils.synthesizeKey("VK_UP", {});
+ let expectedStringAfterCarat = lineValues[5]+newlineString;
+ is(inputNode.value.slice(inputNode.selectionStart), expectedStringAfterCarat,
+ "up arrow from end of multiline");
+
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(inputNode.value.slice(inputNode.selectionStart), "",
+ "down arrow from within multiline");
+
+ // navigate up through input lines
+ EventUtils.synthesizeKey("p", { ctrlKey: true });
+ is(inputNode.value.slice(inputNode.selectionStart), expectedStringAfterCarat,
+ "ctrl-p from end of multiline");
+
+ for (let i = 4; i >= 0; i--) {
+ EventUtils.synthesizeKey("p", { ctrlKey: true });
+ expectedStringAfterCarat = lineValues[i] + newlineString + expectedStringAfterCarat;
+ is(inputNode.value.slice(inputNode.selectionStart), expectedStringAfterCarat,
+ "ctrl-p from within line " + i + " of multiline input");
+ }
+ EventUtils.synthesizeKey("p", { ctrlKey: true });
+ is(inputNode.selectionStart, 0, "reached start of input");
+ is(inputNode.value, inputValue,
+ "no change to multiline input on ctrl-p from beginning of multiline");
+
+ // navigate to end of first line
+ EventUtils.synthesizeKey("e", { ctrlKey: true });
+ let caretPos = inputNode.selectionStart;
+ let expectedStringBeforeCarat = lineValues[0];
+ is(inputNode.value.slice(0, caretPos), expectedStringBeforeCarat,
+ "ctrl-e into multiline input");
+ EventUtils.synthesizeKey("e", { ctrlKey: true });
+ is(inputNode.selectionStart, caretPos,
+ "repeat ctrl-e doesn't change caret position in multiline input");
+
+ // navigate down one line; ctrl-a to the beginning; ctrl-e to end
+ for (let i = 1; i < lineValues.length; i++) {
+ EventUtils.synthesizeKey("n", { ctrlKey: true });
+ EventUtils.synthesizeKey("a", { ctrlKey: true });
+ caretPos = inputNode.selectionStart;
+ expectedStringBeforeCarat += newlineString;
+ is(inputNode.value.slice(0, caretPos), expectedStringBeforeCarat,
+ "ctrl-a to beginning of line " + (i+1) + " in multiline input");
+
+ EventUtils.synthesizeKey("e", { ctrlKey: true });
+ caretPos = inputNode.selectionStart;
+ expectedStringBeforeCarat += lineValues[i];
+ is(inputNode.value.slice(0, caretPos), expectedStringBeforeCarat,
+ "ctrl-e to end of line " + (i+1) + "in multiline input");
+ }
+}
+
+function testNavWithHistory() {
+ // NOTE: Tests does NOT currently define behaviour for ctrl-p/ctrl-n with
+ // caret placed _within_ single line input
+ let values = ['"single line input"',
+ '"a longer single-line input to check caret repositioning"',
+ ['"multi-line"', '"input"', '"here!"'].join("\n"),
+ ];
+ // submit to history
+ for (let i = 0; i < values.length; i++) {
+ jsterm.setInputValue(values[i]);
+ jsterm.execute();
+ }
+ is(inputNode.selectionStart, 0, "caret location at start of empty line");
+
+ EventUtils.synthesizeKey("p", { ctrlKey: true });
+ is(inputNode.selectionStart, values[values.length-1].length,
+ "caret location correct at end of last history input");
+
+ // Navigate backwards history with ctrl-p
+ for (let i = values.length-1; i > 0; i--) {
+ let match = values[i].match(/(\n)/g);
+ if (match) {
+ // multi-line inputs won't update from history unless caret at beginning
+ EventUtils.synthesizeKey("a", { ctrlKey: true });
+ for (let i = 0; i < match.length; i++) {
+ EventUtils.synthesizeKey("p", { ctrlKey: true });
+ }
+ EventUtils.synthesizeKey("p", { ctrlKey: true });
+ } else {
+ // single-line inputs will update from history from end of line
+ EventUtils.synthesizeKey("p", { ctrlKey: true });
+ }
+ is(inputNode.value, values[i-1],
+ "ctrl-p updates inputNode from backwards history values[" + i-1 + "]");
+ }
+ let inputValue = inputNode.value;
+ EventUtils.synthesizeKey("p", { ctrlKey: true });
+ is(inputNode.selectionStart, 0,
+ "ctrl-p at beginning of history moves caret location to beginning of line");
+ is(inputNode.value, inputValue,
+ "no change to input value on ctrl-p from beginning of line");
+
+ // Navigate forwards history with ctrl-n
+ for (let i = 1; i<values.length; i++) {
+ EventUtils.synthesizeKey("n", { ctrlKey: true });
+ is(inputNode.value, values[i],
+ "ctrl-n updates inputNode from forwards history values[" + i + "]");
+ is(inputNode.selectionStart, values[i].length,
+ "caret location correct at end of history input for values[" + i + "]");
+ }
+ EventUtils.synthesizeKey("n", { ctrlKey: true });
+ ok(!inputNode.value, "ctrl-n at end of history updates to empty input");
+
+ // Simulate editing multi-line
+ inputValue = "one\nlinebreak";
+ jsterm.setInputValue(inputValue);
+
+ // Attempt nav within input
+ EventUtils.synthesizeKey("p", { ctrlKey: true });
+ is(inputNode.value, inputValue,
+ "ctrl-p from end of multi-line does not trigger history");
+
+ EventUtils.synthesizeKey("a", { ctrlKey: true });
+ EventUtils.synthesizeKey("p", { ctrlKey: true });
+ is(inputNode.value, values[values.length-1],
+ "ctrl-p from start of multi-line triggers history");
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_817834_add_edited_input_to_history.js b/browser/devtools/webconsole/test/browser_webconsole_bug_817834_add_edited_input_to_history.js
new file mode 100644
index 000000000..98633c327
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_817834_add_edited_input_to_history.js
@@ -0,0 +1,63 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * zmgmoz <zmgmoz@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Test that user input that is not submitted in the command line input is not
+// lost after navigating in history.
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=817834
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 817834";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ testEditedInputHistory(hud);
+});
+
+function testEditedInputHistory(HUD) {
+ let jsterm = HUD.jsterm;
+ let inputNode = jsterm.inputNode;
+ ok(!inputNode.value, "inputNode.value is empty");
+ is(inputNode.selectionStart, 0);
+ is(inputNode.selectionEnd, 0);
+
+ jsterm.setInputValue('"first item"');
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(inputNode.value, '"first item"', "null test history up");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(inputNode.value, '"first item"', "null test history down");
+
+ jsterm.execute();
+ is(inputNode.value, "", "cleared input line after submit");
+
+ jsterm.setInputValue('"editing input 1"');
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(inputNode.value, '"first item"', "test history up");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(inputNode.value, '"editing input 1"',
+ "test history down restores in-progress input");
+
+ jsterm.setInputValue('"second item"');
+ jsterm.execute();
+ jsterm.setInputValue('"editing input 2"');
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(inputNode.value, '"second item"', "test history up");
+ EventUtils.synthesizeKey("VK_UP", {});
+ is(inputNode.value, '"first item"', "test history up");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(inputNode.value, '"second item"', "test history down");
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(inputNode.value, '"editing input 2"',
+ "test history down restores new in-progress input again");
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_837351_securityerrors.js b/browser/devtools/webconsole/test/browser_webconsole_bug_837351_securityerrors.js
new file mode 100644
index 000000000..c5d8b22fb
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_837351_securityerrors.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-bug-837351-security-errors.html";
+
+let test = asyncTest(function* () {
+ yield pushPrefEnv();
+
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ let button = hud.ui.rootElement.querySelector(".webconsole-filter-button[category=\"security\"]");
+ ok(button, "Found security button in the web console");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "Logged blocking mixed active content",
+ text: "Blocked loading mixed active content \"http://example.com/\"",
+ category: CATEGORY_SECURITY,
+ severity: SEVERITY_ERROR
+ },
+ ],
+ });
+});
+
+function pushPrefEnv()
+{
+ let deferred = promise.defer();
+ let options = {'set': [["security.mixed_content.block_active_content", true]]};
+ SpecialPowers.pushPrefEnv(options, deferred.resolve);
+ return deferred.promise;
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_846918_hsts_invalid-headers.js b/browser/devtools/webconsole/test/browser_webconsole_bug_846918_hsts_invalid-headers.js
new file mode 100644
index 000000000..a3911f4b4
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_846918_hsts_invalid-headers.js
@@ -0,0 +1,35 @@
+ /* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* Tests that errors about invalid HSTS security headers are logged
+ * to the web console */
+const TEST_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-bug-846918-hsts-invalid-headers.html";
+const HSTS_INVALID_HEADER_MSG = "The site specified an invalid Strict-Transport-Security header.";
+const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/HTTP_Strict_Transport_Security";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ let results = yield waitForMessages({
+ webconsole: hud,
+ messages: [
+ {
+ name: "Invalid HSTS header error displayed successfully",
+ text: HSTS_INVALID_HEADER_MSG,
+ category: CATEGORY_SECURITY,
+ severity: SEVERITY_WARNING,
+ objects: true,
+ },
+ ],
+ });
+
+ yield testClickOpenNewTab(hud, results);
+});
+
+function testClickOpenNewTab(hud, results) {
+ let warningNode = results[0].clickableElements[0];
+ ok(warningNode, "link element");
+ ok(warningNode.classList.contains("learn-more-link"), "link class name");
+ return simulateMessageLinkClick(warningNode, LEARN_MORE_URI);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_bug_915141_toggle_response_logging_with_keyboard.js b/browser/devtools/webconsole/test/browser_webconsole_bug_915141_toggle_response_logging_with_keyboard.js
new file mode 100644
index 000000000..56b566654
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_915141_toggle_response_logging_with_keyboard.js
@@ -0,0 +1,113 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that the 'Log Request and Response Bodies' buttons can be toggled with keyboard.
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 915141: Toggle log response bodies with keyboard";
+let hud;
+
+function test() {
+ let saveBodiesMenuItem;
+ let saveBodiesContextMenuItem;
+
+ loadTab(TEST_URI).then(({tab: tab}) => {
+ return openConsole(tab);
+ })
+ .then((aHud) => {
+ hud = aHud;
+ saveBodiesMenuItem = hud.ui.rootElement.querySelector("#saveBodies");
+ saveBodiesContextMenuItem = hud.ui.rootElement.querySelector("#saveBodiesContextMenu");
+
+ // Test the context menu action.
+ info("Testing 'Log Request and Response Bodies' menuitem of right click context menu.");
+
+ return openPopup(saveBodiesContextMenuItem);
+ })
+ .then(() => {
+ is(saveBodiesContextMenuItem.getAttribute("checked"), "false",
+ "Context menu: 'log responses' is not checked before action.");
+ is(hud.ui._saveRequestAndResponseBodies, false,
+ "Context menu: Responses are not logged before action.");
+
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ return waitForUpdate(saveBodiesContextMenuItem);
+ })
+ .then(() => {
+ is(saveBodiesContextMenuItem.getAttribute("checked"), "true",
+ "Context menu: 'log responses' is checked after menuitem was selected with keyboard.");
+ is(hud.ui._saveRequestAndResponseBodies, true,
+ "Context menu: Responses are saved after menuitem was selected with keyboard.");
+
+ return openPopup(saveBodiesMenuItem);
+ })
+ .then(() => {
+ // Test the 'Net' menu item.
+ info("Testing 'Log Request and Response Bodies' menuitem of 'Net' menu in the console.");
+ // 'Log Request and Response Bodies' should be selected due to previous test.
+
+ is(saveBodiesMenuItem.getAttribute("checked"), "true",
+ "Console net menu: 'log responses' is checked before action.");
+ is(hud.ui._saveRequestAndResponseBodies, true,
+ "Console net menu: Responses are logged before action.");
+
+ // The correct item is the last one in the menu.
+ EventUtils.synthesizeKey("VK_UP", {});
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ return waitForUpdate(saveBodiesMenuItem);
+ })
+ .then(() => {
+ is(saveBodiesMenuItem.getAttribute("checked"), "false",
+ "Console net menu: 'log responses' is NOT checked after menuitem was selected with keyboard.");
+ is(hud.ui._saveRequestAndResponseBodies, false,
+ "Responses are NOT saved after menuitem was selected with keyboard.");
+ hud = null;
+ })
+ .then(finishTest);
+}
+
+/**
+ * Opens and waits for the menu containing aMenuItem to open.
+ * @param aMenuItem MenuItem
+ * A MenuItem in a menu that should be opened.
+ * @return A promise that's resolved once menu is open.
+ */
+function openPopup(aMenuItem) {
+ let menu = aMenuItem.parentNode;
+
+ let menuOpened = promise.defer();
+ let uiUpdated = promise.defer();
+ // The checkbox menuitem is updated asynchronously on 'popupshowing' event so
+ // it's better to wait for both the update to happen and the menu to open
+ // before continuing or the test might fail due to a race between menu being
+ // shown and the item updated to have the correct state.
+ hud.ui.once("save-bodies-ui-toggled", uiUpdated.resolve);
+ menu.addEventListener("popupshown", function onPopup () {
+ menu.removeEventListener("popupshown", onPopup);
+ menuOpened.resolve();
+ });
+
+ menu.openPopup();
+ return Promise.all([menuOpened.promise, uiUpdated.promise]);
+}
+
+/**
+ * Waits for the settings and menu containing aMenuItem to update.
+ * @param aMenuItem MenuItem
+ * The menuitem that should be updated.
+ * @return A promise that's resolved once the settings and menus are updated.
+ */
+function waitForUpdate(aMenuItem) {
+ info("Waiting for settings update to complete.");
+ let deferred = promise.defer();
+ hud.ui.once("save-bodies-pref-reversed", function () {
+ hud.ui.once("save-bodies-ui-toggled", deferred.resolve);
+ // The checked state is only updated once the popup is shown.
+ aMenuItem.parentNode.openPopup();
+ });
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_cached_autocomplete.js b/browser/devtools/webconsole/test/browser_webconsole_cached_autocomplete.js
new file mode 100644
index 000000000..1c780a3fc
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_cached_autocomplete.js
@@ -0,0 +1,108 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that the cached autocomplete results are used when the new
+// user input is a subset of the existing completion results.
+
+const TEST_URI = "data:text/html;charset=utf8,<p>test cached autocompletion results";
+
+let jsterm;
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ jsterm = hud.jsterm;
+ let input = jsterm.inputNode;
+ let popup = jsterm.autocompletePopup;
+
+ // Test if 'doc' gives 'document'
+ input.value = "doc";
+ input.setSelectionRange(3, 3);
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ is(input.value, "doc", "'docu' completion (input.value)");
+ is(jsterm.completeNode.value, " ument", "'docu' completion (completeNode)");
+
+ // Test typing 'window.'.
+ input.value = "window.";
+ input.setSelectionRange(7, 7);
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ ok(popup.getItems().length > 0, "'window.' gave a list of suggestions")
+
+ yield jsterm.execute("window.docfoobar = true");
+
+ // Test typing 'window.doc'.
+ input.value = "window.doc";
+ input.setSelectionRange(10, 10);
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ let newItems = popup.getItems();
+ ok(newItems.every(function(item) {
+ return item.label != "docfoobar";
+ }), "autocomplete cached results do not contain docfoobar. list has not been updated");
+
+ // Test that backspace does not cause a request to the server
+ input.value = "window.do";
+ input.setSelectionRange(9, 9);
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ newItems = popup.getItems();
+ ok(newItems.every(function(item) {
+ return item.label != "docfoobar";
+ }), "autocomplete cached results do not contain docfoobar. list has not been updated");
+
+ yield jsterm.execute("delete window.docfoobar");
+
+ // Test if 'window.getC' gives 'getComputedStyle'
+ input.value = "window."
+ input.setSelectionRange(7, 7);
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ input.value = "window.getC";
+ input.setSelectionRange(11, 11);
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ newItems = popup.getItems();
+ ok(!newItems.every(function(item) {
+ return item.label != "getComputedStyle";
+ }), "autocomplete results do contain getComputedStyle");
+
+ // Test if 'dump(d' gives non-zero results
+ input.value = "dump(d";
+ input.setSelectionRange(6, 6);
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ ok(popup.getItems().length > 0, "'dump(d' gives non-zero results");
+
+ // Test that 'dump(window.)' works.
+ input.value = "dump(window.)";
+ input.setSelectionRange(12, 12);
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ yield jsterm.execute("window.docfoobar = true");
+
+ // Make sure 'dump(window.doc)' does not contain 'docfoobar'.
+ input.value = "dump(window.doc)";
+ input.setSelectionRange(15, 15);
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ newItems = popup.getItems();
+ ok(newItems.every(function(item) {
+ return item.label != "docfoobar";
+ }), "autocomplete cached results do not contain docfoobar. list has not been updated");
+
+ yield jsterm.execute("delete window.docfoobar");
+
+ jsterm = null;
+});
+
+function complete(type) {
+ let updated = jsterm.once("autocomplete-updated");
+ jsterm.complete(type);
+ return updated;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_cd_iframe.js b/browser/devtools/webconsole/test/browser_webconsole_cd_iframe.js
new file mode 100644
index 000000000..bc919a258
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_cd_iframe.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the cd() jsterm helper function works as expected. See bug 609872.
+
+function test() {
+ let hud;
+
+ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-609872-cd-iframe-parent.html";
+
+ const parentMessages = [{
+ name: "document.title in parent iframe",
+ text: "bug 609872 - iframe parent",
+ category: CATEGORY_OUTPUT,
+ }, {
+ name: "paragraph content",
+ text: "p: test for bug 609872 - iframe parent",
+ category: CATEGORY_OUTPUT,
+ }, {
+ name: "object content",
+ text: "obj: parent!",
+ category: CATEGORY_OUTPUT,
+ }];
+
+ const childMessages = [{
+ name: "document.title in child iframe",
+ text: "bug 609872 - iframe child",
+ category: CATEGORY_OUTPUT,
+ }, {
+ name: "paragraph content",
+ text: "p: test for bug 609872 - iframe child",
+ category: CATEGORY_OUTPUT,
+ }, {
+ name: "object content",
+ text: "obj: child!",
+ category: CATEGORY_OUTPUT,
+ }];
+
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ const {tab} = yield loadTab(TEST_URI);
+ hud = yield openConsole(tab);
+
+ yield executeWindowTest();
+
+ yield waitForMessages({ webconsole: hud, messages: parentMessages });
+
+ info("cd() into the iframe using a selector");
+ hud.jsterm.clearOutput();
+ yield hud.jsterm.execute("cd('iframe')");
+ yield executeWindowTest();
+
+ yield waitForMessages({ webconsole: hud, messages: childMessages });
+
+ info("cd() out of the iframe, reset to default window");
+ hud.jsterm.clearOutput();
+ yield hud.jsterm.execute("cd()");
+ yield executeWindowTest();
+
+ yield waitForMessages({ webconsole: hud, messages: parentMessages });
+
+ info("call cd() with unexpected arguments");
+ hud.jsterm.clearOutput();
+ yield hud.jsterm.execute("cd(document)");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "Cannot cd()",
+ category: CATEGORY_OUTPUT,
+ severity: SEVERITY_ERROR,
+ }],
+ });
+
+ hud.jsterm.clearOutput();
+ yield hud.jsterm.execute("cd('p')");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "Cannot cd()",
+ category: CATEGORY_OUTPUT,
+ severity: SEVERITY_ERROR,
+ }],
+ });
+
+ info("cd() into the iframe using an iframe DOM element");
+ hud.jsterm.clearOutput();
+ yield hud.jsterm.execute("cd($('iframe'))");
+ yield executeWindowTest();
+
+ yield waitForMessages({ webconsole: hud, messages: childMessages });
+
+ info("cd(window.parent)");
+ hud.jsterm.clearOutput();
+ yield hud.jsterm.execute("cd(window.parent)");
+ yield executeWindowTest();
+
+ yield waitForMessages({ webconsole: hud, messages: parentMessages });
+
+ yield closeConsole(tab);
+ }
+
+ function executeWindowTest() {
+ yield hud.jsterm.execute("document.title");
+ yield hud.jsterm.execute("'p: ' + document.querySelector('p').textContent");
+ yield hud.jsterm.execute("'obj: ' + window.foobarBug609872");
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_certificate_messages.js b/browser/devtools/webconsole/test/browser_webconsole_certificate_messages.js
new file mode 100644
index 000000000..8a90876c0
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_certificate_messages.js
@@ -0,0 +1,94 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the Web Console shows weak crypto warnings (SHA-1 Certificate, SSLv3, and RC4)
+
+const TEST_URI = "data:text/html;charset=utf8,Web Console weak crypto warnings test";
+const TEST_URI_PATH = "/browser/browser/devtools/webconsole/test/test-certificate-messages.html";
+
+let gWebconsoleTests = [
+ {url: "https://sha1ee.example.com" + TEST_URI_PATH,
+ name: "SHA1 warning displayed successfully",
+ warning: ["SHA-1"], nowarning: ["SSL 3.0", "RC4"]},
+ {url: "https://ssl3.example.com" + TEST_URI_PATH,
+ name: "SSL3 warning displayed successfully",
+ pref: [["security.tls.version.min", 0]],
+ warning: ["SSL 3.0"], nowarning: ["SHA-1", "RC4"]},
+ {url: "https://rc4.example.com" + TEST_URI_PATH,
+ name: "RC4 warning displayed successfully",
+ pref: [["security.tls.insecure_fallback_hosts", "rc4.example.com"]],
+ warning: ["RC4"], nowarning: ["SHA-1", "SSL 3.0"]},
+ {url: "https://rc4.example.com" + TEST_URI_PATH + "?",
+ name: "Unrestricted RC4 fallback worked",
+ pref: [["security.tls.unrestricted_rc4_fallback", true]],
+ warning: ["RC4"], nowarning: ["SHA-1", "SSL 3.0"]},
+ {url: "https://ssl3rc4.example.com" + TEST_URI_PATH,
+ name: "SSL3 and RC4 warning displayed successfully",
+ pref: [["security.tls.version.min", 0],
+ ["security.tls.insecure_fallback_hosts", "ssl3rc4.example.com"]],
+ warning: ["SSL 3.0", "RC4"], nowarning: ["SHA-1"]},
+ {url: "https://sha256ee.example.com" + TEST_URI_PATH,
+ name: "SSL warnings appropriately not present",
+ warning: [], nowarning: ["SHA-1", "SSL 3.0", "RC4"]},
+];
+const TRIGGER_MSG = "If you haven't seen ssl warnings yet, you won't";
+
+let gHud = undefined, gContentBrowser;
+let gCurrentTest;
+
+function test() {
+ registerCleanupFunction(function () {
+ gHud = gContentBrowser = null;
+ });
+
+ loadTab(TEST_URI).then(({browser}) => {
+ gContentBrowser = browser;
+ openConsole().then(runTestLoop);
+ });
+}
+
+function runTestLoop(theHud) {
+ gCurrentTest = gWebconsoleTests.shift();
+ if (!gCurrentTest) {
+ finishTest();
+ return;
+ }
+ if (!gHud) {
+ gHud = theHud;
+ }
+ gHud.jsterm.clearOutput();
+ gContentBrowser.addEventListener("load", onLoad, true);
+ if (gCurrentTest.pref) {
+ SpecialPowers.pushPrefEnv({"set": gCurrentTest.pref},
+ function() {
+ content.location = gCurrentTest.url;
+ });
+ } else {
+ content.location = gCurrentTest.url;
+ }
+}
+
+function onLoad(aEvent) {
+ gContentBrowser.removeEventListener("load", onLoad, true);
+ let aOutputNode = gHud.outputNode;
+
+ waitForSuccess({
+ name: gCurrentTest.name,
+ validator: function() {
+ if (gHud.outputNode.textContent.indexOf(TRIGGER_MSG) >= 0) {
+ for (let warning of gCurrentTest.warning) {
+ if (gHud.outputNode.textContent.indexOf(warning) < 0) {
+ return false;
+ }
+ }
+ for (let nowarning of gCurrentTest.nowarning) {
+ if (gHud.outputNode.textContent.indexOf(nowarning) >= 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ }).then(runTestLoop);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_change_font_size.js b/browser/devtools/webconsole/test/browser_webconsole_change_font_size.js
new file mode 100644
index 000000000..ab86bb49e
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_change_font_size.js
@@ -0,0 +1,39 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Jennifer Fong <jfong@mozilla.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const TEST_URI = "http://example.com/";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ Services.prefs.setIntPref("devtools.webconsole.fontSize", 10);
+ let hud = yield HUDService.toggleBrowserConsole();
+
+ let inputNode = hud.jsterm.inputNode;
+ let outputNode = hud.jsterm.outputNode;
+ outputNode.focus();
+
+ EventUtils.synthesizeKey("-", { accelKey: true }, hud.iframeWindow);
+ is(inputNode.style.fontSize, "10px", "input font stays at same size with ctrl+-");
+ is(outputNode.style.fontSize, inputNode.style.fontSize, "output font stays at same size with ctrl+-");
+
+ EventUtils.synthesizeKey("=", { accelKey: true }, hud.iframeWindow);
+ is(inputNode.style.fontSize, "11px", "input font increased with ctrl+=");
+ is(outputNode.style.fontSize, inputNode.style.fontSize, "output font stays at same size with ctrl+=");
+
+ EventUtils.synthesizeKey("-", { accelKey: true }, hud.iframeWindow);
+ is(inputNode.style.fontSize, "10px", "font decreased with ctrl+-");
+ is(outputNode.style.fontSize, inputNode.style.fontSize, "output font stays at same size with ctrl+-");
+
+ EventUtils.synthesizeKey("0", { accelKey: true }, hud.iframeWindow);
+ is(inputNode.style.fontSize, "", "font reset with ctrl+0");
+ is(outputNode.style.fontSize, inputNode.style.fontSize, "output font stays at same size with ctrl+0");
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_chrome.js b/browser/devtools/webconsole/test/browser_webconsole_chrome.js
new file mode 100644
index 000000000..f7f3cd0e3
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_chrome.js
@@ -0,0 +1,38 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that code completion works properly in chrome tabs, like about:credits.
+
+"use strict";
+
+function test() {
+ Task.spawn(function*() {
+ const {tab} = yield loadTab("about:config");
+ ok(tab, "tab loaded");
+
+ const hud = yield openConsole(tab);
+ ok(hud, "we have a console");
+ ok(hud.iframeWindow, "we have the console UI window");
+
+ let jsterm = hud.jsterm;
+ ok(jsterm, "we have a jsterm");
+
+ let input = jsterm.inputNode;
+ ok(hud.outputNode, "we have an output node");
+
+ // Test typing 'docu'.
+ input.value = "docu";
+ input.setSelectionRange(4, 4);
+
+ let deferred = promise.defer();
+
+ jsterm.complete(jsterm.COMPLETE_HINT_ONLY, function() {
+ is(jsterm.completeNode.value, " ment", "'docu' completion");
+ deferred.resolve(null);
+ });
+
+ yield deferred.promise;
+ }).then(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_clickable_urls.js b/browser/devtools/webconsole/test/browser_webconsole_clickable_urls.js
new file mode 100644
index 000000000..05f1ee2c9
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_clickable_urls.js
@@ -0,0 +1,85 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// When strings containing URLs are entered into the webconsole,
+// check its output and ensure that the output can be clicked to open those URLs.
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf8,Bug 1005909 - Clickable URLS";
+
+let inputTests = [
+
+ // 0: URL opens page when clicked.
+ {
+ input: "'http://example.com'",
+ output: "http://example.com",
+ expectedTab: "http://example.com/",
+ },
+
+ // 1: URL opens page using https when clicked.
+ {
+ input: "'https://example.com'",
+ output: "https://example.com",
+ expectedTab: "https://example.com/",
+ },
+
+ // 2: URL with port opens page when clicked.
+ {
+ input: "'https://example.com:443'",
+ output: "https://example.com:443",
+ expectedTab: "https://example.com/",
+ },
+
+ // 3: URL containing non-empty path opens page when clicked.
+ {
+ input: "'http://example.com/foo'",
+ output: "http://example.com/foo",
+ expectedTab: "http://example.com/foo",
+ },
+
+ // 4: URL opens page when clicked, even when surrounded by non-URL tokens.
+ {
+ input: "'foo http://example.com bar'",
+ output: "foo http://example.com bar",
+ expectedTab: "http://example.com/",
+ },
+
+ // 5: URL opens page when clicked, and whitespace is be preserved.
+ {
+ input: "'foo\\nhttp://example.com\\nbar'",
+ output: "foo\nhttp://example.com\nbar",
+ expectedTab: "http://example.com/",
+ },
+
+ // 6: URL opens page when clicked when multiple links are present.
+ {
+ input: "'http://example.com http://example.com'",
+ output: "http://example.com http://example.com",
+ expectedTab: "http://example.com/",
+ },
+
+ // 7: URL without scheme does not open page when clicked.
+ {
+ input: "'example.com'",
+ output: "example.com",
+ },
+
+ // 8: URL with invalid scheme does not open page when clicked.
+ {
+ input: "'foo://example.com'",
+ output: "foo://example.com",
+ },
+
+];
+
+function test() {
+ Task.spawn(function*() {
+ let {tab} = yield loadTab(TEST_URI);
+ let hud = yield openConsole(tab);
+ yield checkOutputForInputs(hud, inputTests);
+ inputTests = null;
+ }).then(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_closure_inspection.js b/browser/devtools/webconsole/test/browser_webconsole_closure_inspection.js
new file mode 100644
index 000000000..537ef1a72
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_closure_inspection.js
@@ -0,0 +1,89 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Check that inspecting a closure in the variables view sidebar works when
+// execution is paused.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-closures.html";
+
+let gWebConsole, gJSTerm, gVariablesView;
+
+function test()
+{
+ registerCleanupFunction(() => {
+ gWebConsole = gJSTerm = gVariablesView = null;
+ });
+
+ loadTab(TEST_URI).then(() => {
+ openConsole().then((hud) => {
+ openDebugger().then(({ toolbox, panelWin }) => {
+ let deferred = promise.defer();
+ panelWin.gThreadClient.addOneTimeListener("resumed", (aEvent, aPacket) => {
+ ok(true, "Debugger resumed");
+ deferred.resolve({ toolbox: toolbox, panelWin: panelWin });
+ });
+ return deferred.promise;
+ }).then(({ toolbox, panelWin }) => {
+ let deferred = promise.defer();
+ panelWin.once(panelWin.EVENTS.FETCHED_SCOPES, (aEvent, aPacket) => {
+ ok(true, "Scopes were fetched");
+ toolbox.selectTool("webconsole").then(() => consoleOpened(hud));
+ deferred.resolve();
+ });
+
+ let button = content.document.querySelector("button");
+ ok(button, "button element found");
+ EventUtils.synthesizeMouseAtCenter(button, {}, content);
+
+ return deferred.promise;
+ });
+ })
+ });
+}
+
+function consoleOpened(hud)
+{
+ gWebConsole = hud;
+ gJSTerm = hud.jsterm;
+ gJSTerm.execute("window.george.getName");
+
+ waitForMessages({
+ webconsole: gWebConsole,
+ messages: [{
+ text: "function _pfactory/<.getName()",
+ category: CATEGORY_OUTPUT,
+ objects: true,
+ }],
+ }).then(onExecuteGetName);
+}
+
+function onExecuteGetName(aResults)
+{
+ let clickable = aResults[0].clickableElements[0];
+ ok(clickable, "clickable object found");
+
+ gJSTerm.once("variablesview-fetched", onGetNameFetch);
+ EventUtils.synthesizeMouse(clickable, 2, 2, {}, gWebConsole.iframeWindow);
+}
+
+function onGetNameFetch(aEvent, aVar)
+{
+ gVariablesView = aVar._variablesView;
+ ok(gVariablesView, "variables view object");
+
+ findVariableViewProperties(aVar, [
+ { name: /_pfactory/, value: "" },
+ ], { webconsole: gWebConsole }).then(onExpandClosure);
+}
+
+function onExpandClosure(aResults)
+{
+ let prop = aResults[0].matchedProp;
+ ok(prop, "matched the name property in the variables view");
+
+ gVariablesView.window.focus();
+ gJSTerm.once("sidebar-closed", finishTest);
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_column_numbers.js b/browser/devtools/webconsole/test/browser_webconsole_column_numbers.js
new file mode 100644
index 000000000..bbc5dde81
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_column_numbers.js
@@ -0,0 +1,42 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+ // Check if console provides the right column number alongside line number
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-column.html";
+
+let hud;
+
+function test() {
+ loadTab(TEST_URI).then(() => {
+ openConsole().then(consoleOpened);
+ });
+}
+
+function consoleOpened(aHud) {
+ hud = aHud;
+
+ waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: 'Error Message',
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_ERROR
+ }]
+ }).then(testLocationColumn);
+}
+
+function testLocationColumn() {
+ let messages = hud.outputNode.children;
+ let expected = ['10:6', '10:38', '11:8', '12:10', '13:8', '14:6'];
+
+ for(let i = 0, len = messages.length; i < len; i++) {
+ let msg = messages[i].textContent;
+
+ is(msg.contains(expected[i]), true, 'Found expected line:column of ' + expected[i]);
+ }
+
+ finishTest();
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_completion.js b/browser/devtools/webconsole/test/browser_webconsole_completion.js
new file mode 100644
index 000000000..d68ae0ac3
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_completion.js
@@ -0,0 +1,101 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that code completion works properly.
+
+const TEST_URI = "data:text/html;charset=utf8,<p>test code completion";
+
+let jsterm;
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ jsterm = hud.jsterm;
+ let input = jsterm.inputNode;
+
+ // Test typing 'docu'.
+ input.value = "docu";
+ input.setSelectionRange(4, 4);
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ is(input.value, "docu", "'docu' completion (input.value)");
+ is(jsterm.completeNode.value, " ment", "'docu' completion (completeNode)");
+
+ // Test typing 'docu' and press tab.
+ input.value = "docu";
+ input.setSelectionRange(4, 4);
+ yield complete(jsterm.COMPLETE_FORWARD);
+
+ is(input.value, "document", "'docu' tab completion");
+ is(input.selectionStart, 8, "start selection is alright");
+ is(input.selectionEnd, 8, "end selection is alright");
+ is(jsterm.completeNode.value.replace(/ /g, ""), "", "'docu' completed");
+
+ // Test typing 'window.Ob' and press tab. Just 'window.O' is
+ // ambiguous: could be window.Object, window.Option, etc.
+ input.value = "window.Ob";
+ input.setSelectionRange(9, 9);
+ yield complete(jsterm.COMPLETE_FORWARD);
+
+ is(input.value, "window.Object", "'window.Ob' tab completion");
+
+ // Test typing 'document.getElem'.
+ input.value = "document.getElem";
+ input.setSelectionRange(16, 16);
+ yield complete(jsterm.COMPLETE_FORWARD);
+
+ is(input.value, "document.getElem", "'document.getElem' completion");
+ is(jsterm.completeNode.value, " entsByTagNameNS", "'document.getElem' completion");
+
+ // Test pressing tab another time.
+ yield jsterm.complete(jsterm.COMPLETE_FORWARD);
+
+ is(input.value, "document.getElem", "'document.getElem' completion");
+ is(jsterm.completeNode.value, " entsByTagName", "'document.getElem' another tab completion");
+
+ // Test pressing shift_tab.
+ complete(jsterm.COMPLETE_BACKWARD);
+
+ is(input.value, "document.getElem", "'document.getElem' untab completion");
+ is(jsterm.completeNode.value, " entsByTagNameNS", "'document.getElem' completion");
+
+ jsterm.clearOutput();
+
+ input.value = "docu";
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ is(jsterm.completeNode.value, " ment", "'docu' completion");
+ yield jsterm.execute();
+ is(jsterm.completeNode.value, "", "clear completion on execute()");
+
+ // Test multi-line completion works
+ input.value = "console.log('one');\nconsol";
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ is(jsterm.completeNode.value, " \n e", "multi-line completion");
+
+ // Test non-object autocompletion.
+ input.value = "Object.name.sl";
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ is(jsterm.completeNode.value, " ice", "non-object completion");
+
+ // Test string literal autocompletion.
+ input.value = "'Asimov'.sl";
+ yield complete(jsterm.COMPLETE_HINT_ONLY);
+
+ is(jsterm.completeNode.value, " ice", "string literal completion");
+
+ jsterm = null;
+});
+
+
+function complete(type) {
+ let updated = jsterm.once("autocomplete-updated");
+ jsterm.complete(type);
+ return updated;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_console_api_stackframe.js b/browser/devtools/webconsole/test/browser_webconsole_console_api_stackframe.js
new file mode 100644
index 000000000..499a45d59
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_console_api_stackframe.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the console API messages for console.error()/exception()/assert()
+// include a stackframe. See bug 920116.
+
+function test() {
+ let hud;
+
+ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-api-stackframe.html";
+ const TEST_FILE = TEST_URI.substr(TEST_URI.lastIndexOf("/"));
+
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ const {tab} = yield loadTab(TEST_URI);
+ hud = yield openConsole(tab);
+
+ const stack = [{
+ file: TEST_FILE,
+ fn: "thirdCall",
+ line: /\b2[123]\b/, // 21,22,23
+ }, {
+ file: TEST_FILE,
+ fn: "secondCall",
+ line: 16,
+ }, {
+ file: TEST_FILE,
+ fn: "firstCall",
+ line: 12,
+ }];
+
+ let results = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "foo-log",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ collapsible: false,
+ }, {
+ text: "foo-error",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_ERROR,
+ collapsible: true,
+ stacktrace: stack,
+ }, {
+ text: "foo-exception",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_ERROR,
+ collapsible: true,
+ stacktrace: stack,
+ }, {
+ text: "foo-assert",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_ERROR,
+ collapsible: true,
+ stacktrace: stack,
+ }],
+ });
+
+ let elem = [...results[1].matched][0];
+ ok(elem, "message element");
+
+ let msg = elem._messageObject;
+ ok(msg, "message object");
+
+ ok(msg.collapsed, "message is collapsed");
+
+ msg.toggleDetails();
+
+ ok(!msg.collapsed, "message is not collapsed");
+
+ msg.toggleDetails();
+
+ ok(msg.collapsed, "message is collapsed");
+
+ yield closeConsole(tab);
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_console_custom_styles.js b/browser/devtools/webconsole/test/browser_webconsole_console_custom_styles.js
new file mode 100644
index 000000000..a522fc47b
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_console_custom_styles.js
@@ -0,0 +1,79 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the '%c' modifier works with the console API. See bug 823097.
+
+function test() {
+ let hud;
+
+ const TEST_URI = "data:text/html;charset=utf8,<p>test for " +
+ "console.log('%ccustom styles', 'color:red')";
+
+ const checks = [{
+ // check the basics work
+ style: "color:red;font-size:1.3em",
+ props: { color: true, fontSize: true },
+ sameStyleExpected: true,
+ }, {
+ // check that the url() is not allowed
+ style: "color:blue;background-image:url('http://example.com/test')",
+ props: { color: true, fontSize: false, background: false,
+ backgroundImage: false },
+ sameStyleExpected: false,
+ }, {
+ // check that some properties are not allowed
+ style: "color:pink;position:absolute;top:10px",
+ props: { color: true, position: false, top: false },
+ sameStyleExpected: false,
+ }];
+
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ const {tab} = yield loadTab(TEST_URI);
+ hud = yield openConsole(tab);
+
+ for (let check of checks) {
+ yield checkStyle(check);
+ }
+
+ yield closeConsole(tab);
+ }
+
+ function* checkStyle(check) {
+ hud.jsterm.clearOutput();
+
+ info("checkStyle " + check.style);
+ hud.jsterm.execute("console.log('%cfoobar', \"" + check.style + "\")");
+
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "foobar",
+ category: CATEGORY_WEBDEV,
+ }],
+ });
+
+ let msg = [...result.matched][0];
+ ok(msg, "message element");
+
+ let span = msg.querySelector(".message-body span[style]");
+ ok(span, "span element");
+
+ info("span textContent is: " + span.textContent);
+ isnot(span.textContent.indexOf("foobar"), -1, "span textContent check");
+
+ let outputStyle = span.getAttribute("style").replace(/\s+|;+$/g, "");
+ if (check.sameStyleExpected) {
+ is(outputStyle, check.style, "span style is correct");
+ } else {
+ isnot(outputStyle, check.style, "span style is not the same");
+ }
+
+ for (let prop of Object.keys(check.props)) {
+ is(!!span.style[prop], check.props[prop], "property check for " + prop);
+ }
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_console_extras.js b/browser/devtools/webconsole/test/browser_webconsole_console_extras.js
new file mode 100644
index 000000000..b42437492
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_console_extras.js
@@ -0,0 +1,40 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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 that window.console functions that are not implemented yet do not
+// output anything in the web console and they do not throw any exceptions.
+// See bug 614350.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-extras.html";
+
+function test() {
+ loadTab(TEST_URI).then(() => {
+ openConsole().then(consoleOpened);
+ });
+}
+
+function consoleOpened(hud) {
+ waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "start",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ },
+ {
+ text: "end",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ }).then(() => {
+ let nodes = hud.outputNode.querySelectorAll(".message");
+ is(nodes.length, 2, "only two messages are displayed");
+ finishTest();
+ });
+
+ let button = content.document.querySelector("button");
+ ok(button, "we have the button");
+ EventUtils.sendMouseEvent({ type: "click" }, button, content);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_console_logging_api.js b/browser/devtools/webconsole/test/browser_webconsole_console_logging_api.js
new file mode 100644
index 000000000..aeb73c58e
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_console_logging_api.js
@@ -0,0 +1,101 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that the basic console.log()-style APIs and filtering work.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ hud.jsterm.clearOutput();
+
+ let outputNode = hud.outputNode;
+
+ let methods = ["log", "info", "warn", "error", "exception", "debug"];
+ for (let method of methods) {
+ yield testMethod(method, hud, outputNode);
+ }
+});
+
+function* testMethod(aMethod, aHud, aOutputNode) {
+ let console = content.console;
+
+ aHud.jsterm.clearOutput();
+
+ console[aMethod]("foo-bar-baz");
+ console[aMethod]("baar-baz");
+
+ yield waitForMessages({
+ webconsole: aHud,
+ messages: [{
+ text: "foo-bar-baz",
+ }, {
+ text: "baar-baz",
+ }],
+ });
+
+ setStringFilter("foo", aHud);
+
+ is(aOutputNode.querySelectorAll(".filtered-by-string").length, 1,
+ "1 hidden " + aMethod + " node via string filtering")
+
+ aHud.jsterm.clearOutput();
+
+ // now toggle the current method off - make sure no visible message
+ // TODO: move all filtering tests into a separate test file: see bug 608135
+
+ console[aMethod]("foo-bar-baz");
+ yield waitForMessages({
+ webconsole: aHud,
+ messages: [{
+ text: "foo-bar-baz",
+ }],
+ });
+
+ setStringFilter("", aHud);
+ let filter;
+ switch(aMethod) {
+ case "debug":
+ filter = "log";
+ break;
+ case "exception":
+ filter = "error";
+ break;
+ default:
+ filter = aMethod;
+ break;
+ }
+
+ aHud.setFilterState(filter, false);
+
+ is(aOutputNode.querySelectorAll(".filtered-by-type").length, 1,
+ "1 message hidden for " + aMethod + " (logging turned off)")
+
+ aHud.setFilterState(filter, true);
+
+ is(aOutputNode.querySelectorAll(".message:not(.filtered-by-type)").length, 1,
+ "1 message shown for " + aMethod + " (logging turned on)")
+
+ aHud.jsterm.clearOutput();
+
+ // test for multiple arguments.
+ console[aMethod]("foo", "bar");
+
+ yield waitForMessages({
+ webconsole: aHud,
+ messages: [{
+ text: '"foo" "bar"',
+ category: CATEGORY_WEBDEV,
+ }],
+ })
+}
+
+function setStringFilter(aValue, aHud) {
+ aHud.ui.filterBox.value = aValue;
+ aHud.ui.adjustVisibilityOnSearchStringChange();
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_console_trace_duplicates.js b/browser/devtools/webconsole/test/browser_webconsole_console_trace_duplicates.js
new file mode 100644
index 000000000..76793c3c7
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_console_trace_duplicates.js
@@ -0,0 +1,46 @@
+/* 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/. */
+
+function test() {
+ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug_939783_console_trace_duplicates.html";
+
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ const {tab} = yield loadTab("data:text/html;charset=utf8,<p>hello");
+ const hud = yield openConsole(tab);
+
+ content.location = TEST_URI;
+
+ // NB: Now that stack frames include a column number multiple invocations
+ // on the same line are considered unique. ie:
+ // |foo(); foo();|
+ // will generate two distinct trace entries.
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "console.trace output for foo1()",
+ text: "foo1()",
+ consoleTrace: {
+ file: "test-bug_939783_console_trace_duplicates.html",
+ fn: "foo3()",
+ },
+ }, {
+ name: "console.trace output for foo1()",
+ text: "foo1()",
+ consoleTrace: {
+ file: "test-bug_939783_console_trace_duplicates.html",
+ fn: "foo3()",
+ },
+ }, {
+ name: "console.trace output for foo1b()",
+ text: "foo1b()",
+ consoleTrace: {
+ file: "test-bug_939783_console_trace_duplicates.html",
+ fn: "foo3()",
+ },
+ }],
+ });
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_count.js b/browser/devtools/webconsole/test/browser_webconsole_count.js
new file mode 100644
index 000000000..a4866baa4
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_count.js
@@ -0,0 +1,77 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that console.count() counts as expected. See bug 922208.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-count.html";
+
+function test() {
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ const {tab} = yield loadTab(TEST_URI);
+ const hud = yield openConsole(tab);
+
+ let button = content.document.querySelector("#local");
+ ok(button, "we have the local-tests button");
+ EventUtils.sendMouseEvent({ type: "click" }, button, content);
+ let messages = [];
+ [
+ "start",
+ "<no label>: 2",
+ "console.count() testcounter: 1",
+ "console.count() testcounter: 2",
+ "console.count() testcounter: 3",
+ "console.count() testcounter: 4",
+ "end"
+ ].forEach(function (msg) {
+ messages.push({
+ text: msg,
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG
+ });
+ });
+ messages.push({
+ name: "Three local counts with no label and count=1",
+ text: "<no label>: 1",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ count: 3
+ });
+ yield waitForMessages({
+ webconsole: hud,
+ messages: messages
+ });
+
+ hud.jsterm.clearOutput();
+
+ button = content.document.querySelector("#external");
+ ok(button, "we have the external-tests button");
+ EventUtils.sendMouseEvent({ type: "click" }, button, content);
+ messages = [];
+ [
+ "start",
+ "console.count() testcounter: 5",
+ "console.count() testcounter: 6",
+ "end"
+ ].forEach(function (msg) {
+ messages.push({
+ text: msg,
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG
+ });
+ });
+ messages.push({
+ name: "Two external counts with no label and count=1",
+ text: "<no label>: 1",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ count: 2
+ });
+ yield waitForMessages({
+ webconsole: hud,
+ messages: messages
+ });
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_dont_navigate_on_doubleclick.js b/browser/devtools/webconsole/test/browser_webconsole_dont_navigate_on_doubleclick.js
new file mode 100644
index 000000000..34eb87b00
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_dont_navigate_on_doubleclick.js
@@ -0,0 +1,50 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that if a link in console is double clicked, the console frame doesn't
+// navigate to that destination (bug 975707).
+
+"use strict";
+
+function test() {
+ let originalNetPref = Services.prefs.getBoolPref("devtools.webconsole.filter.networkinfo");
+ registerCleanupFunction(() => {
+ Services.prefs.setBoolPref("devtools.webconsole.filter.networkinfo", originalNetPref);
+ });
+ Services.prefs.setBoolPref("devtools.webconsole.filter.networkinfo", true);
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ const TEST_PAGE_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html" + "?_uniq=" + Date.now();
+
+ const {tab} = yield loadTab("data:text/html;charset=utf8,<p>hello</p>");
+ const hud = yield openConsole(tab);
+
+ content.location = TEST_PAGE_URI;
+
+ let messages = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "Network request message",
+ url: TEST_PAGE_URI,
+ category: CATEGORY_NETWORK
+ }]
+ });
+
+ let networkEventMessage = messages[0].matched.values().next().value;
+ let urlNode = networkEventMessage.querySelector(".url");
+
+ let deferred = promise.defer();
+ urlNode.addEventListener("click", function onClick(aEvent) {
+ urlNode.removeEventListener("click", onClick);
+ ok(aEvent.defaultPrevented, "The default action was prevented.");
+
+ deferred.resolve();
+ });
+
+ EventUtils.synthesizeMouseAtCenter(urlNode, {clickCount: 2}, hud.iframeWindow);
+
+ yield deferred.promise;
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_execution_scope.js b/browser/devtools/webconsole/test/browser_webconsole_execution_scope.js
new file mode 100644
index 000000000..338b17e16
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_execution_scope.js
@@ -0,0 +1,34 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that commands run by the user are executed in content space.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ hud.jsterm.clearOutput();
+ hud.jsterm.execute("window.location.href;");
+
+ let [input, output] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "window.location.href;",
+ category: CATEGORY_INPUT,
+ },
+ {
+ text: TEST_URI,
+ category: CATEGORY_OUTPUT,
+ }],
+ });
+
+ let inputNode = [...input.matched][0];
+ let outputNode = [...output.matched][0];
+ is(inputNode.getAttribute("category"), "input", "input node category is correct");
+ is(outputNode.getAttribute("category"), "output", "output node category is correct");
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_expandable_timestamps.js b/browser/devtools/webconsole/test/browser_webconsole_expandable_timestamps.js
new file mode 100644
index 000000000..efcdaa63c
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_expandable_timestamps.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test for the message timestamps option: check if the preference toggles the
+// display of messages in the console output. See bug 722267.
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for bug 722267 - " +
+ "preference for toggling timestamps in messages";
+const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";
+let hud;
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ hud = yield openConsole();
+ let panel = yield consoleOpened();
+
+ yield onOptionsPanelSelected(panel);
+ onPrefChanged();
+
+ Services.prefs.clearUserPref(PREF_MESSAGE_TIMESTAMP);
+ hud = null;
+});
+
+function consoleOpened()
+{
+ info("console opened");
+ let prefValue = Services.prefs.getBoolPref(PREF_MESSAGE_TIMESTAMP);
+ ok(!prefValue, "messages have no timestamp by default (pref check)");
+ ok(hud.outputNode.classList.contains("hideTimestamps"),
+ "messages have no timestamp (class name check)");
+
+ let toolbox = gDevTools.getToolbox(hud.target);
+ return toolbox.selectTool("options");
+}
+
+function onOptionsPanelSelected(panel)
+{
+ info("options panel opened");
+
+ let prefChanged = gDevTools.once("pref-changed", onPrefChanged);
+
+ let checkbox = panel.panelDoc.getElementById("webconsole-timestamp-messages");
+ checkbox.click();
+
+ return prefChanged;
+}
+
+function onPrefChanged()
+{
+ info("pref changed");
+ let prefValue = Services.prefs.getBoolPref(PREF_MESSAGE_TIMESTAMP);
+ ok(prefValue, "messages have timestamps (pref check)");
+ ok(!hud.outputNode.classList.contains("hideTimestamps"),
+ "messages have timestamps (class name check)");
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_filter_buttons_contextmenu.js b/browser/devtools/webconsole/test/browser_webconsole_filter_buttons_contextmenu.js
new file mode 100644
index 000000000..058b52afa
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_filter_buttons_contextmenu.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the filter button context menu logic works correctly.
+
+const TEST_URI = "http://example.com/";
+
+function test() {
+ loadTab(TEST_URI).then(() => {
+ openConsole().then(testFilterButtons);
+ })
+}
+
+function testFilterButtons(aHud) {
+ let hudBox = aHud.ui.rootElement;
+
+ testRightClick("net", hudBox, aHud)
+ .then(() => testRightClick("css", hudBox, aHud))
+ .then(() => testRightClick("js", hudBox, aHud))
+ .then(() => testRightClick("logging", hudBox, aHud))
+ .then(() => testRightClick("security", hudBox, aHud))
+ .then(finishTest);
+}
+
+function testRightClick(aCategory, hudBox, aHud) {
+ let deferred = promise.defer();
+ let selector = ".webconsole-filter-button[category=\"" + aCategory + "\"]";
+ let button = hudBox.querySelector(selector);
+ let mainButton = getMainButton(button, aHud);
+ let origCheckedState = button.getAttribute("aria-pressed");
+ let contextMenu = aHud.iframeWindow.document.getElementById(aCategory + "-contextmenu");
+
+ function verifyContextMenuIsClosed() {
+ info("verify the context menu is closed");
+ is(button.getAttribute("open"), false, "The context menu for the \"" + aCategory +
+ "\" button is closed");
+ }
+
+ function verifyOriginalCheckedState() {
+ info("verify the button has the original checked state");
+ is(button.getAttribute("aria-pressed"), origCheckedState,
+ "The button state should not have changed");
+ };
+
+ function verifyNewCheckedState() {
+ info("verify the button's checked state has changed");
+ isnot(button.getAttribute("aria-pressed"), origCheckedState,
+ "The button state should have changed");
+ };
+
+ function leftClickToClose() {
+ info("left click the button to close the contextMenu");
+ EventUtils.sendMouseEvent({type: "click"}, button);
+ executeSoon(() => {
+ verifyContextMenuIsClosed();
+ verifyOriginalCheckedState();
+ leftClickToChangeCheckedState();
+ });
+ }
+
+ function leftClickToChangeCheckedState() {
+ info("left click the mainbutton to change checked state");
+ EventUtils.sendMouseEvent({type: "click"}, mainButton);
+ executeSoon(() => {
+ verifyContextMenuIsClosed();
+ verifyNewCheckedState();
+ deferred.resolve();
+ });
+ }
+
+ verifyContextMenuIsClosed();
+ info("right click the button to open the context menu");
+ waitForContextMenu(contextMenu, mainButton, verifyOriginalCheckedState,
+ leftClickToClose);
+ return deferred.promise;
+}
+
+function getMainButton(aTargetButton, aHud) {
+ let anonymousNodes = aHud.ui.document.getAnonymousNodes(aTargetButton);
+ let subbutton;
+
+ for (let i = 0; i < anonymousNodes.length; i++) {
+ let node = anonymousNodes[i];
+ if (node.classList.contains("toolbarbutton-menubutton-button")) {
+ subbutton = node;
+ break;
+ }
+ }
+
+ return subbutton;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_for_of.js b/browser/devtools/webconsole/test/browser_webconsole_for_of.js
new file mode 100644
index 000000000..b5b6304f6
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_for_of.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// A for-of loop in Web Console code can loop over a content NodeList.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-for-of.html";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+ yield testForOf(hud);
+});
+
+function testForOf(hud) {
+ let deferred = promise.defer();
+
+ var jsterm = hud.jsterm;
+ jsterm.execute("{ [x.tagName for (x of document.body.childNodes) if (x.nodeType === 1)].join(' '); }",
+ (node) => {
+ ok(/H1 DIV H2 P/.test(node.textContent),
+ "for-of loop should find all top-level nodes");
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_history.js b/browser/devtools/webconsole/test/browser_webconsole_history.js
new file mode 100644
index 000000000..42d957afb
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_history.js
@@ -0,0 +1,61 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests the console history feature accessed via the up and down arrow keys.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+// Constants used for defining the direction of JSTerm input history navigation.
+const HISTORY_BACK = -1;
+const HISTORY_FORWARD = 1;
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ hud.jsterm.clearOutput();
+
+ let jsterm = hud.jsterm;
+ let input = jsterm.inputNode;
+
+ let executeList = ["document", "window", "window.location"];
+
+ for each (var item in executeList) {
+ input.value = item;
+ yield jsterm.execute();
+ }
+
+ for (var i = executeList.length - 1; i != -1; i--) {
+ jsterm.historyPeruse(HISTORY_BACK);
+ is (input.value, executeList[i], "check history previous idx:" + i);
+ }
+
+ jsterm.historyPeruse(HISTORY_BACK);
+ is (input.value, executeList[0], "test that item is still index 0");
+
+ jsterm.historyPeruse(HISTORY_BACK);
+ is (input.value, executeList[0], "test that item is still still index 0");
+
+ for (var i = 1; i < executeList.length; i++) {
+ jsterm.historyPeruse(HISTORY_FORWARD);
+ is (input.value, executeList[i], "check history next idx:" + i);
+ }
+
+ jsterm.historyPeruse(HISTORY_FORWARD);
+ is (input.value, "", "check input is empty again");
+
+ // Simulate pressing Arrow_Down a few times and then if Arrow_Up shows
+ // the previous item from history again.
+ jsterm.historyPeruse(HISTORY_FORWARD);
+ jsterm.historyPeruse(HISTORY_FORWARD);
+ jsterm.historyPeruse(HISTORY_FORWARD);
+
+ is (input.value, "", "check input is still empty");
+
+ let idxLast = executeList.length - 1;
+ jsterm.historyPeruse(HISTORY_BACK);
+ is (input.value, executeList[idxLast], "check history next idx:" + idxLast);
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_input_field_focus_on_panel_select.js b/browser/devtools/webconsole/test/browser_webconsole_input_field_focus_on_panel_select.js
new file mode 100644
index 000000000..192fc5595
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_input_field_focus_on_panel_select.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that the JS input field is focused when the user switches back to the
+// web console from other tools, see bug 891581.
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf8,<p>hello";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ hud.jsterm.clearOutput();
+
+ is(hud.jsterm.inputNode.hasAttribute("focused"), true,
+ "inputNode should be focused");
+
+ hud.ui.filterBox.focus();
+
+ is(hud.ui.filterBox.hasAttribute("focused"), true,
+ "filterBox should be focused");
+
+ is(hud.jsterm.inputNode.hasAttribute("focused"), false,
+ "inputNode shouldn't be focused");
+
+ yield openDebugger();
+ hud = yield openConsole();
+
+ is(hud.jsterm.inputNode.hasAttribute("focused"), true,
+ "inputNode should be focused");
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_inspect-parsed-documents.js b/browser/devtools/webconsole/test/browser_webconsole_inspect-parsed-documents.js
new file mode 100644
index 000000000..e55e021c2
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_inspect-parsed-documents.js
@@ -0,0 +1,34 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that dynamically created (HTML|XML|SVG)Documents can be inspected by
+// clicking on the object in console (bug 1035198).
+
+"use strict;"
+
+const TEST_CASES = [
+ {
+ input: '(new DOMParser()).parseFromString("<a />", "text/html")',
+ output: "HTMLDocument",
+ inspectable: true,
+ },
+ {
+ input: '(new DOMParser()).parseFromString("<a />", "application/xml")',
+ output: "XMLDocument",
+ inspectable: true,
+ },
+ {
+ input: '(new DOMParser()).parseFromString("<svg></svg>", "image/svg+xml")',
+ output: "SVGDocument",
+ inspectable: true,
+ },
+];
+
+const TEST_URI = "data:text/html;charset=utf8," +
+ "browser_webconsole_inspect-parsed-documents.js";
+add_task(function* () {
+ let {tab} = yield loadTab(TEST_URI);
+ let hud = yield openConsole(tab);
+ yield checkOutputForInputs(hud, TEST_CASES);
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_js_input_expansion.js b/browser/devtools/webconsole/test/browser_webconsole_js_input_expansion.js
new file mode 100644
index 000000000..fe0483233
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_js_input_expansion.js
@@ -0,0 +1,55 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that the input box expands as the user types long lines.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ hud.jsterm.clearOutput();
+
+ let input = hud.jsterm.inputNode;
+ input.focus();
+
+ is(input.getAttribute("multiline"), "true", "multiline is enabled");
+ // Tests if the inputNode expands.
+ input.value = "hello\nworld\n";
+ let length = input.value.length;
+ input.selectionEnd = length;
+ input.selectionStart = length;
+ function getHeight()
+ {
+ return input.clientHeight;
+ }
+ let initialHeight = getHeight();
+ // Performs an "d". This will trigger/test for the input event that should
+ // change the "row" attribute of the inputNode.
+ EventUtils.synthesizeKey("d", {});
+ let newHeight = getHeight();
+ ok(initialHeight < newHeight, "Height changed: " + newHeight);
+
+ // Add some more rows. Tests for the 8 row limit.
+ input.value = "row1\nrow2\nrow3\nrow4\nrow5\nrow6\nrow7\nrow8\nrow9\nrow10\n";
+ length = input.value.length;
+ input.selectionEnd = length;
+ input.selectionStart = length;
+ EventUtils.synthesizeKey("d", {});
+ let newerHeight = getHeight();
+
+ ok(newerHeight > newHeight, "height changed: " + newerHeight);
+
+ // Test if the inputNode shrinks again.
+ input.value = "";
+ EventUtils.synthesizeKey("d", {});
+ let height = getHeight();
+ info("height: " + height);
+ info("initialHeight: " + initialHeight);
+ let finalHeightDifference = Math.abs(initialHeight - height);
+ ok(finalHeightDifference <= 1, "height shrank to original size within 1px");
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_jsterm.js b/browser/devtools/webconsole/test/browser_webconsole_jsterm.js
new file mode 100644
index 000000000..8b9100d0e
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_jsterm.js
@@ -0,0 +1,144 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let jsterm;
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ jsterm = hud.jsterm;
+ yield testJSTerm(hud);
+ jsterm = null;
+});
+
+function checkResult(msg, desc) {
+ let def = promise.defer();
+ waitForMessages({
+ webconsole: jsterm.hud.owner,
+ messages: [{
+ name: desc,
+ category: CATEGORY_OUTPUT,
+ }],
+ }).then(([result]) => {
+ let node = [...result.matched][0].querySelector(".message-body");
+ if (typeof msg == "string") {
+ is(node.textContent.trim(), msg,
+ "correct message shown for " + desc);
+ }
+ else if (typeof msg == "function") {
+ ok(msg(node), "correct message shown for " + desc);
+ }
+
+ def.resolve();
+ });
+ return def.promise;
+}
+
+function testJSTerm(hud)
+{
+ const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
+
+ jsterm.clearOutput();
+ yield jsterm.execute("$('#header').getAttribute('id')");
+ yield checkResult('"header"', "$() worked");
+
+ jsterm.clearOutput();
+ yield jsterm.execute("$$('h1').length");
+ yield checkResult("1", "$$() worked");
+
+ jsterm.clearOutput();
+ yield jsterm.execute("$x('.//*', document.body)[0] == $$('h1')[0]");
+ yield checkResult("true", "$x() worked");
+
+ // no jsterm.clearOutput() here as we clear the output using the clear() fn.
+ yield jsterm.execute("clear()");
+
+ yield waitForSuccess({
+ name: "clear() worked",
+ validator: function()
+ {
+ return jsterm.outputNode.childNodes.length == 0;
+ }
+ });
+
+ jsterm.clearOutput();
+ yield jsterm.execute("keys({b:1})[0] == 'b'");
+ yield checkResult("true", "keys() worked", 1);
+
+ jsterm.clearOutput();
+ yield jsterm.execute("values({b:1})[0] == 1");
+ yield checkResult("true", "values() worked", 1);
+
+ jsterm.clearOutput();
+
+ let openedLinks = 0;
+ let oldOpenLink = hud.openLink;
+ hud.openLink = (url) => {
+ if (url == HELP_URL) {
+ openedLinks++;
+ }
+ };
+
+ yield jsterm.execute("help()");
+ yield jsterm.execute("help");
+ yield jsterm.execute("?");
+
+ let output = jsterm.outputNode.querySelector(".message[category='output']");
+ ok(!output, "no output for help() calls");
+ is(openedLinks, 3, "correct number of pages opened by the help calls");
+ hud.openLink = oldOpenLink;
+
+ jsterm.clearOutput();
+ yield jsterm.execute("pprint({b:2, a:1})");
+ yield checkResult("\" b: 2\n a: 1\"", "pprint()");
+
+ // check instanceof correctness, bug 599940
+ jsterm.clearOutput();
+ yield jsterm.execute("[] instanceof Array");
+ yield checkResult("true", "[] instanceof Array == true");
+
+ jsterm.clearOutput();
+ yield jsterm.execute("({}) instanceof Object");
+ yield checkResult("true", "({}) instanceof Object == true");
+
+ // check for occurrences of Object XRayWrapper, bug 604430
+ jsterm.clearOutput();
+ yield jsterm.execute("document");
+ yield checkResult(function(node) {
+ return node.textContent.search(/\[object xraywrapper/i) == -1;
+ }, "document - no XrayWrapper");
+
+ // check that pprint(window) and keys(window) don't throw, bug 608358
+ jsterm.clearOutput();
+ yield jsterm.execute("pprint(window)");
+ yield checkResult(null, "pprint(window)");
+
+ jsterm.clearOutput();
+ yield jsterm.execute("keys(window)");
+ yield checkResult(null, "keys(window)");
+
+ // bug 614561
+ jsterm.clearOutput();
+ yield jsterm.execute("pprint('hi')");
+ yield checkResult("\" 0: \"h\"\n 1: \"i\"\"", "pprint('hi')");
+
+ // check that pprint(function) shows function source, bug 618344
+ jsterm.clearOutput();
+ yield jsterm.execute("pprint(function() { var someCanaryValue = 42; })");
+ yield checkResult(function(node) {
+ return node.textContent.indexOf("someCanaryValue") > -1;
+ }, "pprint(function) shows source");
+
+ // check that an evaluated null produces "null", bug 650780
+ jsterm.clearOutput();
+ yield jsterm.execute("null");
+ yield checkResult("null", "null is null");
+
+ jsterm.clearOutput();
+ yield jsterm.execute("undefined");
+ yield checkResult("undefined", "undefined is printed");
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_live_filtering_of_message_types.js b/browser/devtools/webconsole/test/browser_webconsole_live_filtering_of_message_types.js
new file mode 100644
index 000000000..02b79b6e9
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_live_filtering_of_message_types.js
@@ -0,0 +1,55 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that the message type filter checkboxes work.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ hud.jsterm.clearOutput();
+
+ let console = content.console;
+
+ for (let i = 0; i < 50; i++) {
+ console.log("foobarz #" + i);
+ }
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "foobarz #49",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ is(hud.outputNode.children.length, 50, "number of messages");
+
+ hud.setFilterState("log", false);
+ is(countMessageNodes(hud), 0, "the log nodes are hidden when the " +
+ "corresponding filter is switched off");
+
+ hud.setFilterState("log", true);
+ is(countMessageNodes(hud), 50, "the log nodes reappear when the " +
+ "corresponding filter is switched on");
+});
+
+function countMessageNodes(hud) {
+ let messageNodes = hud.outputNode.querySelectorAll(".message");
+ let displayedMessageNodes = 0;
+ let view = hud.iframeWindow;
+ for (let i = 0; i < messageNodes.length; i++) {
+ let computedStyle = view.getComputedStyle(messageNodes[i], null);
+ if (computedStyle.display !== "none") {
+ displayedMessageNodes++;
+ }
+ }
+
+ return displayedMessageNodes;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_live_filtering_on_search_strings.js b/browser/devtools/webconsole/test/browser_webconsole_live_filtering_on_search_strings.js
new file mode 100644
index 000000000..fb229075a
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_live_filtering_on_search_strings.js
@@ -0,0 +1,96 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that the text filter box works.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ hud.jsterm.clearOutput();
+
+ let console = content.console;
+
+ for (let i = 0; i < 50; i++) {
+ console.log("http://www.example.com/ " + i);
+ }
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "http://www.example.com/ 49",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ })
+
+ is(hud.outputNode.children.length, 50, "number of messages");
+
+ setStringFilter(hud, "http");
+ isnot(countMessageNodes(hud), 0, "the log nodes are not hidden when the " +
+ "search string is set to \"http\"");
+
+ setStringFilter(hud, "hxxp");
+ is(countMessageNodes(hud), 0, "the log nodes are hidden when the search " +
+ "string is set to \"hxxp\"");
+
+ setStringFilter(hud, "ht tp");
+ isnot(countMessageNodes(hud), 0, "the log nodes are not hidden when the " +
+ "search string is set to \"ht tp\"");
+
+ setStringFilter(hud, " zzzz zzzz ");
+ is(countMessageNodes(hud), 0, "the log nodes are hidden when the search " +
+ "string is set to \" zzzz zzzz \"");
+
+ setStringFilter(hud, "");
+ isnot(countMessageNodes(hud), 0, "the log nodes are not hidden when the " +
+ "search string is removed");
+
+ setStringFilter(hud, "\u9f2c");
+ is(countMessageNodes(hud), 0, "the log nodes are hidden when searching " +
+ "for weasels");
+
+ setStringFilter(hud, "\u0007");
+ is(countMessageNodes(hud), 0, "the log nodes are hidden when searching for " +
+ "the bell character");
+
+ setStringFilter(hud, '"foo"');
+ is(countMessageNodes(hud), 0, "the log nodes are hidden when searching for " +
+ 'the string "foo"');
+
+ setStringFilter(hud, "'foo'");
+ is(countMessageNodes(hud), 0, "the log nodes are hidden when searching for " +
+ "the string 'foo'");
+
+ setStringFilter(hud, "foo\"bar'baz\"boo'");
+ is(countMessageNodes(hud), 0, "the log nodes are hidden when searching for " +
+ "the string \"foo\"bar'baz\"boo'\"");
+});
+
+function countMessageNodes(hud) {
+ let outputNode = hud.outputNode;
+
+ let messageNodes = outputNode.querySelectorAll(".message");
+ let displayedMessageNodes = 0;
+ let view = hud.iframeWindow;
+ for (let i = 0; i < messageNodes.length; i++) {
+ let computedStyle = view.getComputedStyle(messageNodes[i], null);
+ if (computedStyle.display !== "none") {
+ displayedMessageNodes++;
+ }
+ }
+
+ return displayedMessageNodes;
+}
+
+function setStringFilter(hud, aValue)
+{
+ hud.ui.filterBox.value = aValue;
+ hud.ui.adjustVisibilityOnSearchStringChange();
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_log_file_filter.js b/browser/devtools/webconsole/test/browser_webconsole_log_file_filter.js
new file mode 100644
index 000000000..906bb2b48
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_log_file_filter.js
@@ -0,0 +1,82 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the text filter box works to filter based on filenames
+// where the logs were generated.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug_923281_console_log_filter.html";
+
+let hud;
+
+"use strict";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ hud = yield openConsole();
+ yield consoleOpened();
+
+ testLiveFilteringOnSearchStrings();
+
+ hud = null;
+});
+
+function consoleOpened() {
+ let console = content.console;
+ console.log("sentinel log");
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "sentinel log",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG
+ }],
+ })
+}
+
+function testLiveFilteringOnSearchStrings() {
+ is(hud.outputNode.children.length, 4, "number of messages");
+
+ setStringFilter("random");
+ is(countMessageNodes(), 1, "the log nodes not containing string " +
+ "\"random\" are hidden");
+
+ setStringFilter("test2.js");
+ is(countMessageNodes(), 2, "show only log nodes containing string " +
+ "\"test2.js\" or log nodes created from files with filename " +
+ "containing \"test2.js\" as substring.");
+
+ setStringFilter("test1");
+ is(countMessageNodes(), 2, "show only log nodes containing string " +
+ "\"test1\" or log nodes created from files with filename " +
+ "containing \"test1\" as substring.");
+
+ setStringFilter("");
+ is(countMessageNodes(), 4, "show all log nodes on setting filter string " +
+ "as \"\".");
+}
+
+function countMessageNodes() {
+ let outputNode = hud.outputNode;
+
+ let messageNodes = outputNode.querySelectorAll(".message");
+ content.console.log(messageNodes.length);
+ let displayedMessageNodes = 0;
+ let view = hud.iframeWindow;
+ for (let i = 0; i < messageNodes.length; i++) {
+ let computedStyle = view.getComputedStyle(messageNodes[i], null);
+ if (computedStyle.display !== "none") {
+ displayedMessageNodes++;
+ }
+ }
+
+ return displayedMessageNodes;
+}
+
+function setStringFilter(aValue)
+{
+ hud.ui.filterBox.value = aValue;
+ hud.ui.adjustVisibilityOnSearchStringChange();
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_message_node_id.js b/browser/devtools/webconsole/test/browser_webconsole_message_node_id.js
new file mode 100644
index 000000000..0a94538e6
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_message_node_id.js
@@ -0,0 +1,27 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+
+ hud.jsterm.execute("console.log('a log message')");
+
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "a log message",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ let msg = [...result.matched][0];
+ ok(msg.getAttribute("id"), "log message has an ID");
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_netlogging.js b/browser/devtools/webconsole/test/browser_webconsole_netlogging.js
new file mode 100644
index 000000000..7cf95822e
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_netlogging.js
@@ -0,0 +1,213 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Julian Viereck <jviereck@mozilla.com>
+ * Patrick Walton <pcwalton@mozilla.com>
+ * Mihai Șucan <mihai.sucan@gmail.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// Tests that network log messages bring up the network panel.
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console network logging tests";
+
+const TEST_NETWORK_REQUEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-network-request.html";
+
+const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test/test-image.png";
+
+const TEST_DATA_JSON_CONTENT =
+ '{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ] }';
+
+let lastRequest = null;
+let requestCallback = null;
+let browser, hud;
+
+function test()
+{
+ loadTab(TEST_URI).then((tab) => {
+ browser = tab.browser;
+
+ openConsole().then((aHud) => {
+ hud = aHud;
+
+ HUDService.lastFinishedRequest.callback = requestCallbackWrapper;
+
+ executeSoon(testPageLoad);
+ });
+ });
+}
+
+function requestCallbackWrapper(aRequest)
+{
+ lastRequest = aRequest;
+
+ hud.ui.webConsoleClient.getResponseContent(lastRequest.actor,
+ function(aResponse) {
+ lastRequest.response.content = aResponse.content;
+ lastRequest.discardResponseBody = aResponse.contentDiscarded;
+
+ hud.ui.webConsoleClient.getRequestPostData(lastRequest.actor,
+ function(aResponse) {
+ lastRequest.request.postData = aResponse.postData;
+ lastRequest.discardRequestBody = aResponse.postDataDiscarded;
+
+ if (requestCallback) {
+ requestCallback();
+ }
+ });
+ });
+}
+
+function testPageLoad()
+{
+ requestCallback = function() {
+ // Check if page load was logged correctly.
+ ok(lastRequest, "Page load was logged");
+
+ is(lastRequest.request.url, TEST_NETWORK_REQUEST_URI,
+ "Logged network entry is page load");
+ is(lastRequest.request.method, "GET", "Method is correct");
+ ok(!lastRequest.request.postData.text, "No request body was stored");
+ ok(lastRequest.discardRequestBody, "Request body was discarded");
+ ok(!lastRequest.response.content.text, "No response body was stored");
+ ok(lastRequest.discardResponseBody, "Response body was discarded");
+
+ lastRequest = null;
+ requestCallback = null;
+ executeSoon(testPageLoadBody);
+ };
+
+ content.location = TEST_NETWORK_REQUEST_URI;
+}
+
+function testPageLoadBody()
+{
+ // Turn on logging of request bodies and check again.
+ hud.ui.setSaveRequestAndResponseBodies(true).then(() => {
+ ok(hud.ui._saveRequestAndResponseBodies,
+ "The saveRequestAndResponseBodies property was successfully set.");
+
+ testPageLoadBodyAfterSettingUpdate();
+ });
+}
+
+function testPageLoadBodyAfterSettingUpdate()
+{
+ let loaded = false;
+ let requestCallbackInvoked = false;
+
+ requestCallback = function() {
+ ok(lastRequest, "Page load was logged again");
+ ok(!lastRequest.discardResponseBody, "Response body was not discarded");
+ is(lastRequest.response.content.text.indexOf("<!DOCTYPE HTML>"), 0,
+ "Response body's beginning is okay");
+
+ lastRequest = null;
+ requestCallback = null;
+ requestCallbackInvoked = true;
+
+ if (loaded) {
+ executeSoon(testXhrGet);
+ }
+ };
+
+ browser.addEventListener("load", function onLoad() {
+ browser.removeEventListener("load", onLoad, true);
+ loaded = true;
+
+ if (requestCallbackInvoked) {
+ executeSoon(testXhrGet);
+ }
+ }, true);
+
+ content.location.reload();
+}
+
+function testXhrGet()
+{
+ requestCallback = function() {
+ ok(lastRequest, "testXhrGet() was logged");
+ is(lastRequest.request.method, "GET", "Method is correct");
+ ok(!lastRequest.request.postData.text, "No request body was sent");
+ ok(!lastRequest.discardRequestBody, "Request body was not discarded");
+ is(lastRequest.response.content.text, TEST_DATA_JSON_CONTENT,
+ "Response is correct");
+
+ lastRequest = null;
+ requestCallback = null;
+ executeSoon(testXhrPost);
+ };
+
+ // Start the XMLHttpRequest() GET test.
+ content.wrappedJSObject.testXhrGet();
+}
+
+function testXhrPost()
+{
+ requestCallback = function() {
+ ok(lastRequest, "testXhrPost() was logged");
+ is(lastRequest.request.method, "POST", "Method is correct");
+ is(lastRequest.request.postData.text, "Hello world!",
+ "Request body was logged");
+ is(lastRequest.response.content.text, TEST_DATA_JSON_CONTENT,
+ "Response is correct");
+
+ lastRequest = null;
+ requestCallback = null;
+ executeSoon(testFormSubmission);
+ };
+
+ // Start the XMLHttpRequest() POST test.
+ content.wrappedJSObject.testXhrPost();
+}
+
+function testFormSubmission()
+{
+ // Start the form submission test. As the form is submitted, the page is
+ // loaded again. Bind to the load event to catch when this is done.
+ requestCallback = function() {
+ ok(lastRequest, "testFormSubmission() was logged");
+ is(lastRequest.request.method, "POST", "Method is correct");
+ isnot(lastRequest.request.postData.text.
+ indexOf("Content-Type: application/x-www-form-urlencoded"), -1,
+ "Content-Type is correct");
+ isnot(lastRequest.request.postData.text.
+ indexOf("Content-Length: 20"), -1, "Content-length is correct");
+ isnot(lastRequest.request.postData.text.
+ indexOf("name=foo+bar&age=144"), -1, "Form data is correct");
+ is(lastRequest.response.content.text.indexOf("<!DOCTYPE HTML>"), 0,
+ "Response body's beginning is okay");
+
+ executeSoon(testNetworkPanel);
+ };
+
+ let form = content.document.querySelector("form");
+ ok(form, "we have the HTML form");
+ form.submit();
+}
+
+function testNetworkPanel()
+{
+ // Open the NetworkPanel. The functionality of the NetworkPanel is tested
+ // within separate test files.
+ let networkPanel = hud.ui.openNetworkPanel(hud.ui.filterBox, lastRequest);
+
+ networkPanel.panel.addEventListener("popupshown", function onPopupShown() {
+ networkPanel.panel.removeEventListener("popupshown", onPopupShown, true);
+
+ is(hud.ui.filterBox._netPanel, networkPanel,
+ "Network panel stored on anchor node");
+ ok(true, "NetworkPanel was opened");
+
+ // All tests are done. Shutdown.
+ networkPanel.panel.hidePopup();
+ lastRequest = null;
+ HUDService.lastFinishedRequest.callback = null;
+ browser = requestCallback = hud = null;
+ executeSoon(finishTest);
+ }, true);
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_network_panel.js b/browser/devtools/webconsole/test/browser_webconsole_network_panel.js
new file mode 100644
index 000000000..0cea84fea
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_network_panel.js
@@ -0,0 +1,541 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that the network panel works.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+const TEST_IMG = "http://example.com/browser/browser/devtools/webconsole/test/test-image.png";
+const TEST_ENCODING_ISO_8859_1 = "http://example.com/browser/browser/devtools/webconsole/test/test-encoding-ISO-8859-1.html";
+
+const TEST_IMG_BASE64 =
+ "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAVRJREFU" +
+ "OI2lk7FLw0AUxr+YpC1CBqcMWfsvCCLdXFzqEJCgDl1EQRGxg9AhSBEJONhFhG52UCuFDjq5dxD8" +
+ "FwoO0qGDOBQkl7vLOeWa2EQDffDBvTu+373Hu1OEEJgntGgxGD6J+7fLXKbt5VNUyhsKAChRBQcP" +
+ "FVFeWskFGH694mZroCQqCLlAwPxcgJBP254CmAD5B7C7dgHLMLF3uzoL4DQEod+Z5sP1FizDxGgy" +
+ "BqfhLID9AahX29J89bwPFgMsSEAQglAf9WobhPpScbPXr4FQHyzIADTsDizDRMPuIOC+zEeTMZo9" +
+ "BwH3EfAMACccbtfGaDKGZZg423yUZrdrg3EqxQlPr0BTdTR7joREN2uqnlBmCwW1hIJagtev4f3z" +
+ "A16/JvfiigMSYyzqJXlw/XKUyOORMUaBor6YavgdjKa8xGOnidadmwtwsnMu18q83/kHSou+bFND" +
+ "Dr4AAAAASUVORK5CYII=";
+
+let testDriver, hud;
+
+function test() {
+ loadTab(TEST_URI).then(() => {
+ openConsole().then(testNetworkPanel);
+ });
+}
+
+function testNetworkPanel(aHud) {
+ hud = aHud;
+ testDriver = testGen();
+ testDriver.next();
+}
+
+function checkIsVisible(aPanel, aList) {
+ for (let id in aList) {
+ let node = aPanel.document.getElementById(id);
+ let isVisible = aList[id];
+ is(node.style.display, (isVisible ? "block" : "none"), id + " isVisible=" + isVisible);
+ }
+}
+
+function checkNodeContent(aPanel, aId, aContent) {
+ let node = aPanel.document.getElementById(aId);
+ if (node == null) {
+ ok(false, "Tried to access node " + aId + " that doesn't exist!");
+ }
+ else if (node.textContent.indexOf(aContent) != -1) {
+ ok(true, "checking content of " + aId);
+ }
+ else {
+ ok(false, "Got false value for " + aId + ": " + node.textContent + " doesn't have " + aContent);
+ }
+}
+
+function checkNodeKeyValue(aPanel, aId, aKey, aValue) {
+ let node = aPanel.document.getElementById(aId);
+
+ let headers = node.querySelectorAll("th");
+ for (let i = 0; i < headers.length; i++) {
+ if (headers[i].textContent == (aKey + ":")) {
+ is(headers[i].nextElementSibling.textContent, aValue,
+ "checking content of " + aId + " for key " + aKey);
+ return;
+ }
+ }
+
+ ok(false, "content check failed for " + aId + ", key " + aKey);
+}
+
+function testGen() {
+ let filterBox = hud.ui.filterBox;
+
+ let httpActivity = {
+ updates: [],
+ discardRequestBody: true,
+ discardResponseBody: true,
+ startedDateTime: (new Date()).toISOString(),
+ request: {
+ url: "http://www.testpage.com",
+ method: "GET",
+ cookies: [],
+ headers: [
+ { name: "foo", value: "bar" },
+ ],
+ },
+ response: {
+ headers: [],
+ content: {},
+ cookies: [],
+ },
+ timings: {},
+ };
+
+ let networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+
+ is(filterBox._netPanel, networkPanel,
+ "Network panel stored on the anchor object");
+
+ networkPanel._onUpdate = function() {
+ networkPanel._onUpdate = null;
+ executeSoon(function() {
+ testDriver.next();
+ });
+ };
+
+ yield undefined;
+
+ info("test 1");
+
+ checkIsVisible(networkPanel, {
+ requestCookie: false,
+ requestFormData: false,
+ requestBody: false,
+ responseContainer: false,
+ responseBody: false,
+ responseNoBody: false,
+ responseImage: false,
+ responseImageCached: false
+ });
+
+ checkNodeContent(networkPanel, "header", "http://www.testpage.com");
+ checkNodeContent(networkPanel, "header", "GET");
+ checkNodeKeyValue(networkPanel, "requestHeadersContent", "foo", "bar");
+
+ // Test request body.
+ info("test 2: request body");
+ httpActivity.discardRequestBody = false;
+ httpActivity.request.postData = { text: "hello world" };
+ networkPanel.update();
+
+ checkIsVisible(networkPanel, {
+ requestBody: true,
+ requestFormData: false,
+ requestCookie: false,
+ responseContainer: false,
+ responseBody: false,
+ responseNoBody: false,
+ responseImage: false,
+ responseImageCached: false
+ });
+ checkNodeContent(networkPanel, "requestBodyContent", "hello world");
+
+ // Test response header.
+ info("test 3: response header");
+ httpActivity.timings.wait = 10;
+ httpActivity.response.httpVersion = "HTTP/3.14";
+ httpActivity.response.status = 999;
+ httpActivity.response.statusText = "earthquake win";
+ httpActivity.response.content.mimeType = "text/html";
+ httpActivity.response.headers.push(
+ { name: "Content-Type", value: "text/html" },
+ { name: "leaveHouses", value: "true" }
+ );
+
+ networkPanel.update();
+
+ checkIsVisible(networkPanel, {
+ requestBody: true,
+ requestFormData: false,
+ requestCookie: false,
+ responseContainer: true,
+ responseBody: false,
+ responseNoBody: false,
+ responseImage: false,
+ responseImageCached: false
+ });
+
+ checkNodeContent(networkPanel, "header", "HTTP/3.14 999 earthquake win");
+ checkNodeKeyValue(networkPanel, "responseHeadersContent", "leaveHouses", "true");
+ checkNodeContent(networkPanel, "responseHeadersInfo", "10ms");
+
+ info("test 4");
+
+ httpActivity.discardResponseBody = false;
+ httpActivity.timings.receive = 2;
+ networkPanel.update();
+
+ checkIsVisible(networkPanel, {
+ requestBody: true,
+ requestCookie: false,
+ requestFormData: false,
+ responseContainer: true,
+ responseBody: false,
+ responseNoBody: false,
+ responseImage: false,
+ responseImageCached: false
+ });
+
+ info("test 5");
+
+ httpActivity.updates.push("responseContent", "eventTimings");
+ networkPanel.update();
+
+ checkNodeContent(networkPanel, "responseNoBodyInfo", "2ms");
+ checkIsVisible(networkPanel, {
+ requestBody: true,
+ requestCookie: false,
+ responseContainer: true,
+ responseBody: false,
+ responseNoBody: true,
+ responseImage: false,
+ responseImageCached: false
+ });
+
+ networkPanel.panel.hidePopup();
+
+ // Second run: Test for cookies and response body.
+ info("test 6: cookies and response body");
+ httpActivity.request.cookies.push(
+ { name: "foo", value: "bar" },
+ { name: "hello", value: "world" }
+ );
+ httpActivity.response.content.text = "get out here";
+
+ networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+ is(filterBox._netPanel, networkPanel,
+ "Network panel stored on httpActivity object");
+
+ networkPanel._onUpdate = function() {
+ networkPanel._onUpdate = null;
+ executeSoon(function() {
+ testDriver.next();
+ });
+ };
+
+ yield undefined;
+
+ checkIsVisible(networkPanel, {
+ requestBody: true,
+ requestFormData: false,
+ requestCookie: true,
+ responseContainer: true,
+ responseCookie: false,
+ responseBody: true,
+ responseNoBody: false,
+ responseImage: false,
+ responseImageCached: false
+ });
+
+ checkNodeKeyValue(networkPanel, "requestCookieContent", "foo", "bar");
+ checkNodeKeyValue(networkPanel, "requestCookieContent", "hello", "world");
+ checkNodeContent(networkPanel, "responseBodyContent", "get out here");
+ checkNodeContent(networkPanel, "responseBodyInfo", "2ms");
+
+ networkPanel.panel.hidePopup();
+
+ // Third run: Test for response cookies.
+ info("test 6b: response cookies");
+ httpActivity.response.cookies.push(
+ { name: "foobar", value: "boom" },
+ { name: "foobaz", value: "omg" }
+ );
+
+ networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+ is(filterBox._netPanel, networkPanel,
+ "Network panel stored on httpActivity object");
+
+ networkPanel._onUpdate = function() {
+ networkPanel._onUpdate = null;
+ executeSoon(function() {
+ testDriver.next();
+ });
+ };
+
+ yield undefined;
+
+ checkIsVisible(networkPanel, {
+ requestBody: true,
+ requestFormData: false,
+ requestCookie: true,
+ responseContainer: true,
+ responseCookie: true,
+ responseBody: true,
+ responseNoBody: false,
+ responseImage: false,
+ responseImageCached: false,
+ responseBodyFetchLink: false,
+ });
+
+ checkNodeKeyValue(networkPanel, "responseCookieContent", "foobar", "boom");
+ checkNodeKeyValue(networkPanel, "responseCookieContent", "foobaz", "omg");
+
+ networkPanel.panel.hidePopup();
+
+ // Check image request.
+ info("test 7: image request");
+ httpActivity.response.headers[1].value = "image/png";
+ httpActivity.response.content.mimeType = "image/png";
+ httpActivity.response.content.text = TEST_IMG_BASE64;
+ httpActivity.request.url = TEST_IMG;
+
+ networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+ networkPanel._onUpdate = function() {
+ networkPanel._onUpdate = null;
+ executeSoon(function() {
+ testDriver.next();
+ });
+ };
+
+ yield undefined;
+
+ checkIsVisible(networkPanel, {
+ requestBody: true,
+ requestFormData: false,
+ requestCookie: true,
+ responseContainer: true,
+ responseBody: false,
+ responseNoBody: false,
+ responseImage: true,
+ responseImageCached: false,
+ responseBodyFetchLink: false,
+ });
+
+ let imgNode = networkPanel.document.getElementById("responseImageNode");
+ is(imgNode.getAttribute("src"), "data:image/png;base64," + TEST_IMG_BASE64,
+ "Displayed image is correct");
+
+ function checkImageResponseInfo() {
+ checkNodeContent(networkPanel, "responseImageInfo", "2ms");
+ checkNodeContent(networkPanel, "responseImageInfo", "16x16px");
+ }
+
+ // Check if the image is loaded already.
+ imgNode.addEventListener("load", function onLoad() {
+ imgNode.removeEventListener("load", onLoad, false);
+ checkImageResponseInfo();
+ networkPanel.panel.hidePopup();
+ testDriver.next();
+ }, false);
+ yield undefined;
+
+ // Check cached image request.
+ info("test 8: cached image request");
+ httpActivity.response.httpVersion = "HTTP/1.1";
+ httpActivity.response.status = 304;
+ httpActivity.response.statusText = "Not Modified";
+
+ networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+ networkPanel._onUpdate = function() {
+ networkPanel._onUpdate = null;
+ executeSoon(function() {
+ testDriver.next();
+ });
+ };
+
+ yield undefined;
+
+ checkIsVisible(networkPanel, {
+ requestBody: true,
+ requestFormData: false,
+ requestCookie: true,
+ responseContainer: true,
+ responseBody: false,
+ responseNoBody: false,
+ responseImage: false,
+ responseImageCached: true
+ });
+
+ imgNode = networkPanel.document.getElementById("responseImageCachedNode");
+ is(imgNode.getAttribute("src"), "data:image/png;base64," + TEST_IMG_BASE64,
+ "Displayed image is correct");
+
+ networkPanel.panel.hidePopup();
+
+ // Test sent form data.
+ info("test 9: sent form data");
+ httpActivity.request.postData.text = [
+ "Content-Type: application/x-www-form-urlencoded",
+ "Content-Length: 59",
+ "name=rob&age=20"
+ ].join("\n");
+
+ networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+ networkPanel._onUpdate = function() {
+ networkPanel._onUpdate = null;
+ executeSoon(function() {
+ testDriver.next();
+ });
+ };
+
+ yield undefined;
+
+ checkIsVisible(networkPanel, {
+ requestBody: false,
+ requestFormData: true,
+ requestCookie: true,
+ responseContainer: true,
+ responseBody: false,
+ responseNoBody: false,
+ responseImage: false,
+ responseImageCached: true
+ });
+
+ checkNodeKeyValue(networkPanel, "requestFormDataContent", "name", "rob");
+ checkNodeKeyValue(networkPanel, "requestFormDataContent", "age", "20");
+ networkPanel.panel.hidePopup();
+
+ // Test no space after Content-Type:
+ info("test 10: no space after Content-Type header in post data");
+ httpActivity.request.postData.text = "Content-Type:application/x-www-form-urlencoded\n";
+
+ networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+ networkPanel._onUpdate = function() {
+ networkPanel._onUpdate = null;
+ executeSoon(function() {
+ testDriver.next();
+ });
+ };
+
+ yield undefined;
+
+ checkIsVisible(networkPanel, {
+ requestBody: false,
+ requestFormData: true,
+ requestCookie: true,
+ responseContainer: true,
+ responseBody: false,
+ responseNoBody: false,
+ responseImage: false,
+ responseImageCached: true
+ });
+
+ networkPanel.panel.hidePopup();
+
+ // Test cached data.
+
+ info("test 11: cached data");
+
+ httpActivity.request.url = TEST_ENCODING_ISO_8859_1;
+ httpActivity.response.headers[1].value = "application/json";
+ httpActivity.response.content.mimeType = "application/json";
+ httpActivity.response.content.text = "my cached data is here!";
+
+ networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+ networkPanel._onUpdate = function() {
+ networkPanel._onUpdate = null;
+ executeSoon(function() {
+ testDriver.next();
+ });
+ };
+
+ yield undefined;
+
+ checkIsVisible(networkPanel, {
+ requestBody: false,
+ requestFormData: true,
+ requestCookie: true,
+ responseContainer: true,
+ responseBody: false,
+ responseBodyCached: true,
+ responseNoBody: false,
+ responseImage: false,
+ responseImageCached: false
+ });
+
+ checkNodeContent(networkPanel, "responseBodyCachedContent",
+ "my cached data is here!");
+
+ networkPanel.panel.hidePopup();
+
+ // Test a response with a content type that can't be displayed in the
+ // NetworkPanel.
+ info("test 12: unknown content type");
+ httpActivity.response.headers[1].value = "application/x-shockwave-flash";
+ httpActivity.response.content.mimeType = "application/x-shockwave-flash";
+
+ networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+ networkPanel._onUpdate = function() {
+ networkPanel._onUpdate = null;
+ executeSoon(function() {
+ testDriver.next();
+ });
+ };
+
+ yield undefined;
+
+ checkIsVisible(networkPanel, {
+ requestBody: false,
+ requestFormData: true,
+ requestCookie: true,
+ responseContainer: true,
+ responseBody: false,
+ responseBodyCached: false,
+ responseBodyUnknownType: true,
+ responseNoBody: false,
+ responseImage: false,
+ responseImageCached: false
+ });
+
+ let responseString =
+ WCU_l10n.getFormatStr("NetworkPanel.responseBodyUnableToDisplay.content",
+ ["application/x-shockwave-flash"]);
+ checkNodeContent(networkPanel, "responseBodyUnknownTypeContent", responseString);
+ networkPanel.panel.hidePopup();
+
+ /*
+
+ // This test disabled. See bug 603620.
+
+ // Test if the NetworkPanel figures out the content type based on an URL as
+ // well.
+ delete httpActivity.response.header["Content-Type"];
+ httpActivity.url = "http://www.test.com/someCrazyFile.swf?done=right&ending=txt";
+
+ networkPanel = hud.ui.openNetworkPanel(filterBox, httpActivity);
+ networkPanel.isDoneCallback = function NP_doneCallback() {
+ networkPanel.isDoneCallback = null;
+ testDriver.next();
+ }
+
+ yield undefined;
+
+ checkIsVisible(networkPanel, {
+ requestBody: false,
+ requestFormData: true,
+ requestCookie: true,
+ responseContainer: true,
+ responseBody: false,
+ responseBodyCached: false,
+ responseBodyUnknownType: true,
+ responseNoBody: false,
+ responseImage: false,
+ responseImageCached: false
+ });
+
+ // Systems without Flash installed will return an empty string here. Ignore.
+ if (networkPanel.document.getElementById("responseBodyUnknownTypeContent").textContent !== "")
+ checkNodeContent(networkPanel, "responseBodyUnknownTypeContent", responseString);
+ else
+ ok(true, "Flash not installed");
+
+ networkPanel.panel.hidePopup(); */
+
+ // All done!
+ testDriver = hud = null;
+ executeSoon(finishTest);
+
+ yield undefined;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_notifications.js b/browser/devtools/webconsole/test/browser_webconsole_notifications.js
new file mode 100644
index 000000000..c2be3ab8f
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_notifications.js
@@ -0,0 +1,77 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>Web Console test for notifications";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let consoleOpened = promise.defer();
+ let gotEvents = waitForEvents(consoleOpened.promise);
+ let hud = yield openConsole().then(() => consoleOpened.resolve());
+
+ yield gotEvents;
+});
+
+function waitForEvents(onConsoleOpened) {
+ let deferred = promise.defer();
+
+ function webConsoleCreated(aID)
+ {
+ Services.obs.removeObserver(observer, "web-console-created");
+ ok(HUDService.getHudReferenceById(aID), "We have a hud reference");
+ content.wrappedJSObject.console.log("adding a log message");
+ }
+
+ function webConsoleDestroyed(aID)
+ {
+ Services.obs.removeObserver(observer, "web-console-destroyed");
+ ok(!HUDService.getHudReferenceById(aID), "We do not have a hud reference");
+ executeSoon(deferred.resolve);
+ }
+
+ function webConsoleMessage(aID, aNodeID)
+ {
+ Services.obs.removeObserver(observer, "web-console-message-created");
+ ok(aID, "we have a console ID");
+ is(typeof aNodeID, "string", "message node id is a string");
+ onConsoleOpened.then(closeConsole);
+ }
+
+ let observer = {
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+
+ observe: function observe(aSubject, aTopic, aData)
+ {
+ aSubject = aSubject.QueryInterface(Ci.nsISupportsString);
+
+ switch(aTopic) {
+ case "web-console-created":
+ webConsoleCreated(aSubject.data);
+ break;
+ case "web-console-destroyed":
+ webConsoleDestroyed(aSubject.data);
+ break;
+ case "web-console-message-created":
+ webConsoleMessage(aSubject, aData);
+ break;
+ default:
+ break;
+ }
+ },
+
+ init: function init()
+ {
+ Services.obs.addObserver(this, "web-console-created", false);
+ Services.obs.addObserver(this, "web-console-destroyed", false);
+ Services.obs.addObserver(this, "web-console-message-created", false);
+ }
+ }
+
+ observer.init();
+
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_open-links-without-callback.js b/browser/devtools/webconsole/test/browser_webconsole_open-links-without-callback.js
new file mode 100644
index 000000000..225a340ef
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_open-links-without-callback.js
@@ -0,0 +1,52 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that if a link without an onclick callback is clicked the link is
+// opened in a new tab and no exception occurs (bug 999236).
+
+"use strict";
+
+function test() {
+ function* runner() {
+ const TEST_EVAL_STRING = "document";
+ const TEST_PAGE_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+ const {tab} = yield loadTab(TEST_PAGE_URI);
+ const hud = yield openConsole(tab);
+
+ hud.jsterm.execute(TEST_EVAL_STRING);
+
+ const EXPECTED_OUTPUT = new RegExp("HTMLDocument \.+");
+
+ let messages = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "JS eval output",
+ text: EXPECTED_OUTPUT,
+ category: CATEGORY_OUTPUT,
+ }],
+ });
+
+ let messageNode = messages[0].matched.values().next().value;
+
+ // The correct anchor is second in the message node; the first anchor has
+ // class .cm-variable. Ignore the first one by not matching anchors that
+ // have the class .cm-variable.
+ let urlNode = messageNode.querySelector("a:not(.cm-variable)");
+
+ let linkOpened = false;
+ let oldOpenUILinkIn = window.openUILinkIn;
+ window.openUILinkIn = function(aLink) {
+ if (aLink == TEST_PAGE_URI) {
+ linkOpened = true;
+ }
+ }
+
+ EventUtils.synthesizeMouseAtCenter(urlNode, {}, hud.iframeWindow);
+
+ ok(linkOpened, "Clicking the URL opens the desired page");
+ window.openUILinkIn = oldOpenUILinkIn;
+ }
+
+ Task.spawn(runner).then(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_01.js b/browser/devtools/webconsole/test/browser_webconsole_output_01.js
new file mode 100644
index 000000000..f9ec8ff38
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_01.js
@@ -0,0 +1,125 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("null");
+
+// Test the webconsole output for various types of objects.
+
+const TEST_URI = "data:text/html;charset=utf8,test for console output - 01";
+
+let {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
+
+let LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_LENGTH;
+let LONG_STRING_INITIAL_LENGTH = DebuggerServer.LONG_STRING_INITIAL_LENGTH;
+DebuggerServer.LONG_STRING_LENGTH = 100;
+DebuggerServer.LONG_STRING_INITIAL_LENGTH = 50;
+
+let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 4)).join("a");
+let initialString = longString.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
+
+let inputTests = [
+ // 0
+ {
+ input: "'hello \\nfrom \\rthe \\\"string world!'",
+ output: "\"hello \nfrom \rthe \"string world!\"",
+ },
+
+ // 1
+ {
+ // unicode test
+ input: "'\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165'",
+ output: "\"\xFA\u1E47\u0129\xE7\xF6d\xEA \u021B\u0115\u0219\u0165\"",
+ },
+
+ // 2
+ {
+ input: "'" + longString + "'",
+ output: '"' + initialString + "\"[\u2026]",
+ printOutput: initialString,
+ },
+
+ // 3
+ {
+ input: "''",
+ output: '""',
+ printOutput: '""',
+ },
+
+ // 4
+ {
+ input: "0",
+ output: "0",
+ },
+
+ // 5
+ {
+ input: "'0'",
+ output: '"0"',
+ },
+
+ // 6
+ {
+ input: "42",
+ output: "42",
+ },
+
+ // 7
+ {
+ input: "'42'",
+ output: '"42"',
+ },
+
+ // 8
+ {
+ input: "/foobar/",
+ output: "/foobar/",
+ inspectable: true,
+ },
+
+ // 9
+ {
+ input: "Symbol()",
+ output: "Symbol()"
+ },
+
+ // 10
+ {
+ input: "Symbol('foo')",
+ output: "Symbol(foo)"
+ },
+
+ // 11
+ {
+ input: "Symbol.iterator",
+ output: "Symbol(Symbol.iterator)"
+ },
+];
+
+longString = initialString = null;
+
+function test() {
+ requestLongerTimeout(2);
+
+ registerCleanupFunction(() => {
+ DebuggerServer.LONG_STRING_LENGTH = LONG_STRING_LENGTH;
+ DebuggerServer.LONG_STRING_INITIAL_LENGTH = LONG_STRING_INITIAL_LENGTH;
+ });
+
+ Task.spawn(function*() {
+ let {tab} = yield loadTab(TEST_URI);
+ let hud = yield openConsole(tab);
+ return checkOutputForInputs(hud, inputTests);
+ }).then(finishUp);
+}
+
+function finishUp() {
+ longString = initialString = inputTests = null;
+ finishTest();
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_02.js b/browser/devtools/webconsole/test/browser_webconsole_output_02.js
new file mode 100644
index 000000000..88c4df245
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_02.js
@@ -0,0 +1,160 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test the webconsole output for various types of objects.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-02.html";
+
+let inputTests = [
+ // 0 - native named function
+ {
+ input: "document.getElementById",
+ output: "function getElementById()",
+ printOutput: "function getElementById() {\n [native code]\n}",
+ inspectable: true,
+ variablesViewLabel: "getElementById()",
+ },
+
+ // 1 - anonymous function
+ {
+ input: "(function() { return 42; })",
+ output: "function ()",
+ printOutput: "function () { return 42; }",
+ inspectable: true,
+ },
+
+ // 2 - named function
+ {
+ input: "window.testfn1",
+ output: "function testfn1()",
+ printOutput: "function testfn1() { return 42; }",
+ inspectable: true,
+ variablesViewLabel: "testfn1()",
+ },
+
+ // 3 - anonymous function, but spidermonkey gives us an inferred name.
+ {
+ input: "testobj1.testfn2",
+ output: "function testobj1.testfn2()",
+ printOutput: "function () { return 42; }",
+ inspectable: true,
+ variablesViewLabel: "testobj1.testfn2()",
+ },
+
+ // 4 - named function with custom display name
+ {
+ input: "window.testfn3",
+ output: "function testfn3DisplayName()",
+ printOutput: "function testfn3() { return 42; }",
+ inspectable: true,
+ variablesViewLabel: "testfn3DisplayName()",
+ },
+
+ // 5 - basic array
+ {
+ input: "window.array1",
+ output: 'Array [ 1, 2, 3, "a", "b", "c", "4", "5" ]',
+ printOutput: "1,2,3,a,b,c,4,5",
+ inspectable: true,
+ variablesViewLabel: "Array[8]",
+ },
+
+ // 6 - array with objects
+ {
+ input: "window.array2",
+ output: 'Array [ "a", HTMLDocument \u2192 test-console-output-02.html, <body>, ' +
+ "DOMStringMap[0], DOMTokenList[0] ]",
+ printOutput: '"a,[object HTMLDocument],[object HTMLBodyElement],' +
+ '[object DOMStringMap],"',
+ inspectable: true,
+ variablesViewLabel: "Array[5]",
+ },
+
+ // 7 - array with more than 10 elements
+ {
+ input: "window.array3",
+ output: 'Array [ 1, Window \u2192 test-console-output-02.html, null, "a", "b", ' +
+ 'undefined, false, "", -Infinity, testfn3DisplayName(), 3 more\u2026 ]',
+ printOutput: '"1,[object Window],,a,b,,false,,-Infinity,' +
+ 'function testfn3() { return 42; },[object Object],foo,bar"',
+ inspectable: true,
+ variablesViewLabel: "Array[13]",
+ },
+
+ // 8 - array with holes and a cyclic reference
+ {
+ input: "window.array4",
+ output: 'Array [ <5 empty slots>, "test", Array[7] ]',
+ printOutput: '",,,,,test,"',
+ inspectable: true,
+ variablesViewLabel: "Array[7]",
+ },
+
+ // 9
+ {
+ input: "window.typedarray1",
+ output: 'Int32Array [ 1, 287, 8651, 40983, 8754 ]',
+ printOutput: "[object Int32Array]",
+ inspectable: true,
+ variablesViewLabel: "Int32Array[5]",
+ },
+
+ // 10 - Set with cyclic reference
+ {
+ input: "window.set1",
+ output: 'Set [ 1, 2, null, Array[13], "a", "b", undefined, <head>, Set[9] ]',
+ printOutput: "[object Set]",
+ inspectable: true,
+ variablesViewLabel: "Set[9]",
+ },
+
+ // 11 - Object with cyclic reference and a getter
+ {
+ input: "window.testobj2",
+ output: 'Object { a: "b", c: "d", e: 1, f: "2", foo: Object, bar: Object, ' +
+ "getterTest: Getter }",
+ printOutput: "[object Object]",
+ inspectable: true,
+ variablesViewLabel: "Object",
+ },
+
+ // 12 - Object with more than 10 properties
+ {
+ input: "window.testobj3",
+ output: 'Object { a: "b", c: "d", e: 1, f: "2", g: true, h: null, i: undefined, ' +
+ 'j: "", k: StyleSheetList[0], l: NodeList[5], 2 more\u2026 }',
+ printOutput: "[object Object]",
+ inspectable: true,
+ variablesViewLabel: "Object",
+ },
+
+ // 13 - Object with a non-enumerable property that we do not show
+ {
+ input: "window.testobj4",
+ output: 'Object { a: "b", c: "d", 1 more\u2026 }',
+ printOutput: "[object Object]",
+ inspectable: true,
+ variablesViewLabel: "Object",
+ },
+
+ // 14 - Map with cyclic references
+ {
+ input: "window.map1",
+ output: 'Map { a: "b", HTMLCollection[2]: Object, Map[3]: Set[9] }',
+ printOutput: "[object Map]",
+ inspectable: true,
+ variablesViewLabel: "Map[3]",
+ },
+];
+
+function test() {
+ requestLongerTimeout(2);
+ Task.spawn(function*() {
+ const {tab} = yield loadTab(TEST_URI);
+ const hud = yield openConsole(tab);
+ yield checkOutputForInputs(hud, inputTests);
+ inputTests = null;
+ }).then(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_03.js b/browser/devtools/webconsole/test/browser_webconsole_output_03.js
new file mode 100644
index 000000000..40f8cf334
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_03.js
@@ -0,0 +1,165 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test the webconsole output for various types of objects.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-03.html";
+
+let inputTests = [
+
+ // 0
+ {
+ input: "document",
+ output: "HTMLDocument \u2192 " + TEST_URI,
+ printOutput: "[object HTMLDocument]",
+ inspectable: true,
+ noClick: true,
+ },
+
+ // 1
+ {
+ input: "window",
+ output: "Window \u2192 " + TEST_URI,
+ printOutput: "[object Window",
+ inspectable: true,
+ noClick: true,
+ },
+
+ // 2
+ {
+ input: "document.body",
+ output: "<body>",
+ printOutput: "[object HTMLBodyElement]",
+ inspectable: true,
+ noClick: true,
+ },
+
+ // 3
+ {
+ input: "document.body.dataset",
+ output: "DOMStringMap { }",
+ printOutput: "[object DOMStringMap]",
+ inspectable: true,
+ variablesViewLabel: "DOMStringMap[0]",
+ },
+
+ // 4
+ {
+ input: "document.body.classList",
+ output: "DOMTokenList [ ]",
+ printOutput: '""',
+ inspectable: true,
+ variablesViewLabel: "DOMTokenList[0]",
+ },
+
+ // 5
+ {
+ input: "window.location.href",
+ output: '"' + TEST_URI + '"',
+ noClick: true,
+ },
+
+ // 6
+ {
+ input: "window.location",
+ output: "Location \u2192 " + TEST_URI,
+ printOutput: TEST_URI,
+ inspectable: true,
+ variablesViewLabel: "Location \u2192 test-console-output-03.html",
+ },
+
+ // 7
+ {
+ input: "document.body.attributes",
+ output: "NamedNodeMap [ ]",
+ printOutput: "[object NamedNodeMap]",
+ inspectable: true,
+ variablesViewLabel: "NamedNodeMap[0]",
+ },
+
+ // 8
+ {
+ input: "document.styleSheets",
+ output: "StyleSheetList [ ]",
+ printOutput: "[object StyleSheetList",
+ inspectable: true,
+ variablesViewLabel: "StyleSheetList[0]",
+ },
+
+ // 9
+ {
+ input: "testBodyClassName()",
+ output: '<body class="test1 tezt2">',
+ printOutput: "[object HTMLBodyElement]",
+ inspectable: true,
+ noClick: true,
+ },
+
+ // 10
+ {
+ input: "testBodyID()",
+ output: '<body class="test1 tezt2" id="foobarid">',
+ printOutput: "[object HTMLBodyElement]",
+ inspectable: true,
+ noClick: true,
+ },
+
+ // 11
+ {
+ input: "document.body.classList",
+ output: 'DOMTokenList [ "test1", "tezt2" ]',
+ printOutput: '"test1 tezt2"',
+ inspectable: true,
+ variablesViewLabel: "DOMTokenList[2]",
+ },
+
+ // 12
+ {
+ input: "testBodyDataset()",
+ output: '<body class="test1 tezt2" id="foobarid"' +
+ ' data-preview="zuzu&quot;&lt;a&gt;foo">',
+ printOutput: "[object HTMLBodyElement]",
+ inspectable: true,
+ noClick: true,
+ },
+
+ // 13
+ {
+ input: "document.body.dataset",
+ output: 'DOMStringMap { preview: "zuzu"<a>foo" }',
+ printOutput: "[object DOMStringMap]",
+ inspectable: true,
+ variablesViewLabel: "DOMStringMap[1]",
+ },
+
+ // 14
+ {
+ input: "document.body.attributes",
+ output: 'NamedNodeMap [ class="test1 tezt2", id="foobarid", ' +
+ 'data-preview="zuzu&quot;&lt;a&gt;foo" ]',
+ printOutput: "[object NamedNodeMap]",
+ inspectable: true,
+ variablesViewLabel: "NamedNodeMap[3]",
+ },
+
+ // 15
+ {
+ input: "document.body.attributes[0]",
+ output: 'class="test1 tezt2"',
+ printOutput: "[object Attr]",
+ inspectable: true,
+ variablesViewLabel: 'class="test1 tezt2"',
+ },
+];
+
+function test() {
+ requestLongerTimeout(2);
+ Task.spawn(function*() {
+ const {tab} = yield loadTab(TEST_URI);
+ const hud = yield openConsole(tab);
+ yield checkOutputForInputs(hud, inputTests);
+ inputTests = null;
+ }).then(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_04.js b/browser/devtools/webconsole/test/browser_webconsole_output_04.js
new file mode 100644
index 000000000..f50f4d073
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_04.js
@@ -0,0 +1,127 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("null");
+
+// Test the webconsole output for various types of objects.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-04.html";
+
+let inputTests = [
+ // 0
+ {
+ input: "testTextNode()",
+ output: '#text "hello world!"',
+ printOutput: "[object Text]",
+ inspectable: true,
+ noClick: true,
+ },
+
+ // 1
+ {
+ input: "testCommentNode()",
+ output: /<!--\s+- Any copyright /,
+ printOutput: "[object Comment]",
+ inspectable: true,
+ noClick: true,
+ },
+
+ // 2
+ {
+ input: "testDocumentFragment()",
+ output: 'DocumentFragment [ <div#foo1.bar>, <div#foo3> ]',
+ printOutput: "[object DocumentFragment]",
+ inspectable: true,
+ variablesViewLabel: "DocumentFragment[2]",
+ },
+
+ // 3
+ {
+ input: "testError()",
+ output: "TypeError: window.foobar is not a function\n" +
+ "Stack trace:\n" +
+ "testError@" + TEST_URI + ":44",
+ printOutput: '"TypeError: window.foobar is not a function"',
+ inspectable: true,
+ variablesViewLabel: "TypeError",
+ },
+
+ // 4
+ {
+ input: "testDOMException()",
+ output: 'DOMException [SyntaxError: "An invalid or illegal string was specified"',
+ printOutput: '"SyntaxError: An invalid or illegal string was specified"',
+ inspectable: true,
+ variablesViewLabel: "SyntaxError",
+ },
+
+ // 5
+ {
+ input: "testCSSStyleDeclaration()",
+ output: 'CSS2Properties { color: "green", font-size: "2em" }',
+ printOutput: "[object CSS2Properties]",
+ inspectable: true,
+ noClick: true,
+ },
+
+ // 6
+ {
+ input: "testStyleSheetList()",
+ output: "StyleSheetList [ CSSStyleSheet ]",
+ printOutput: "[object StyleSheetList",
+ inspectable: true,
+ variablesViewLabel: "StyleSheetList[1]",
+ },
+
+ // 7
+ {
+ input: "document.styleSheets[0]",
+ output: "CSSStyleSheet",
+ printOutput: "[object CSSStyleSheet]",
+ inspectable: true,
+ },
+
+ // 8
+ {
+ input: "document.styleSheets[0].cssRules",
+ output: "CSSRuleList [ CSSStyleRule, CSSMediaRule ]",
+ printOutput: "[object CSSRuleList",
+ inspectable: true,
+ variablesViewLabel: "CSSRuleList[2]",
+ },
+
+ // 9
+ {
+ input: "document.styleSheets[0].cssRules[0]",
+ output: 'CSSStyleRule "p, div"',
+ printOutput: "[object CSSStyleRule",
+ inspectable: true,
+ variablesViewLabel: "CSSStyleRule",
+ },
+
+ // 10
+ {
+ input: "document.styleSheets[0].cssRules[1]",
+ output: 'CSSMediaRule "print"',
+ printOutput: "[object CSSMediaRule",
+ inspectable: true,
+ variablesViewLabel: "CSSMediaRule",
+ },
+];
+
+function test() {
+ requestLongerTimeout(2);
+ Task.spawn(function*() {
+ const {tab} = yield loadTab(TEST_URI);
+ const hud = yield openConsole(tab);
+ yield checkOutputForInputs(hud, inputTests);
+ inputTests = null;
+ }).then(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_05.js b/browser/devtools/webconsole/test/browser_webconsole_output_05.js
new file mode 100644
index 000000000..c985b7b34
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_05.js
@@ -0,0 +1,130 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test the webconsole output for various types of objects.
+
+const TEST_URI = "data:text/html;charset=utf8,test for console output - 05";
+const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
+
+let dateNow = Date.now();
+
+let inputTests = [
+ // 0
+ {
+ input: "/foo?b*\\s\"ar/igym",
+ output: "/foo?b*\\s\"ar/gimy",
+ printOutput: "/foo?b*\\s\"ar/gimy",
+ inspectable: true,
+ },
+
+ // 1
+ {
+ input: "null",
+ output: "null",
+ },
+
+ // 2
+ {
+ input: "undefined",
+ output: "undefined",
+ },
+
+ // 3
+ {
+ input: "true",
+ output: "true",
+ },
+
+ // 4
+ {
+ input: "new Boolean(false)",
+ output: "false",
+ inspectable: true,
+ },
+
+ // 5
+ {
+ input: "new Date(" + dateNow + ")",
+ output: "Date " + (new Date(dateNow)).toISOString(),
+ printOutput: (new Date(dateNow)).toString(),
+ inspectable: true,
+ },
+
+ // 6
+ {
+ input: "new Date('test')",
+ output: "Invalid Date",
+ printOutput: "Invalid Date",
+ inspectable: true,
+ variablesViewLabel: "Invalid Date",
+ },
+
+ // 7
+ {
+ input: "Date.prototype",
+ output: "Date",
+ printOutput: "Invalid Date",
+ inspectable: true,
+ variablesViewLabel: "Date",
+ },
+
+ // 8
+ {
+ input: "new Number(43)",
+ output: "43",
+ inspectable: true,
+ },
+
+ // 9
+ {
+ input: "new String('hello')",
+ output: 'String [ "h", "e", "l", "l", "o" ]',
+ printOutput: "hello",
+ inspectable: true,
+ variablesViewLabel: "String[5]"
+ },
+
+ // 9
+ {
+ // XXX: Can't test fulfilled and rejected promises, because promises get
+ // settled on the next tick of the event loop.
+ input: "new Promise(function () {})",
+ output: 'Promise { <state>: "pending" }',
+ printOutput: "[object Promise]",
+ inspectable: true,
+ variablesViewLabel: "Promise"
+ },
+
+ // 10
+ {
+ input: "(function () { var p = new Promise(function () {}); p.foo = 1; return p; }())",
+ output: 'Promise { <state>: "pending", foo: 1 }',
+ printOutput: "[object Promise]",
+ inspectable: true,
+ variablesViewLabel: "Promise"
+ },
+
+ //11
+ {
+ input: "new Object({1: 'this\\nis\\nsupposed\\nto\\nbe\\na\\nvery\\nlong\\nstring\\n,shown\\non\\na\\nsingle\\nline', 2: 'a shorter string', 3: 100})",
+ output: 'Object { 1: "this is supposed to be a very long ' + ELLIPSIS + '", 2: "a shorter string", 3: 100 }',
+ printOutput: "[object Object]",
+ inspectable: false,
+ }
+];
+
+function test() {
+ requestLongerTimeout(2);
+ Task.spawn(function*() {
+ let {tab} = yield loadTab(TEST_URI);
+ let hud = yield openConsole(tab);
+ return checkOutputForInputs(hud, inputTests);
+ }).then(finishUp);
+}
+
+function finishUp() {
+ inputTests = dateNow = null;
+ finishTest();
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_06.js b/browser/devtools/webconsole/test/browser_webconsole_output_06.js
new file mode 100644
index 000000000..082b53dcc
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_06.js
@@ -0,0 +1,127 @@
+ /* vim: set 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 webconsole output for various arrays.
+
+const TEST_URI = "data:text/html;charset=utf8,test for console output - 06";
+const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data;
+const test_str_in = "SHOW\\nALL\\nOF\\nTHIS\\nON\\nA\\nSINGLE\\nLINE ONLY. ESCAPE ALL NEWLINE";
+const test_str_out = "SHOW ALL OF THIS ON A SINGLE LINE O" + ELLIPSIS;
+
+let inputTests = [
+ // 1 - array with empty slots only
+ {
+ input: 'Array(5)',
+ output: 'Array [ <5 empty slots> ]',
+ printOutput: ',,,,',
+ inspectable: true,
+ variablesViewLabel: "Array[5]",
+ },
+ // 2 - array with one empty slot at the beginning
+ {
+ input: '[,1,2,3]',
+ output: 'Array [ <1 empty slot>, 1, 2, 3 ]',
+ printOutput: ",1,2,3",
+ inspectable: true,
+ variablesViewLabel: "Array[4]",
+ },
+ // 3 - array with multiple consecutive empty slots at the beginning
+ {
+ input: '[,,,3,4,5]',
+ output: 'Array [ <3 empty slots>, 3, 4, 5 ]',
+ printOutput: ",,,3,4,5",
+ inspectable: true,
+ variablesViewLabel: "Array[6]",
+ },
+ // 4 - array with one empty slot at the middle
+ {
+ input: '[0,1,,3,4,5]',
+ output: 'Array [ 0, 1, <1 empty slot>, 3, 4, 5 ]',
+ printOutput: "0,1,,3,4,5",
+ inspectable: true,
+ variablesViewLabel: "Array[6]",
+ },
+ // 5 - array with multiple successive empty slots at the middle
+ {
+ input: '[0,1,,,,5]',
+ output: 'Array [ 0, 1, <3 empty slots>, 5 ]',
+ printOutput: "0,1,,,,5",
+ inspectable: true,
+ variablesViewLabel: "Array[6]",
+ },
+ // 6 - array with multiple non successive single empty slots
+ {
+ input: '[0,,2,,4,5]',
+ output: 'Array [ 0, <1 empty slot>, 2, <1 empty slot>, 4, 5 ]',
+ printOutput: "0,,2,,4,5",
+ inspectable: true,
+ variablesViewLabel: "Array[6]",
+ },
+ // 7 - array with multiple multi-slot holes
+ {
+ input: '[0,,,3,,,,7,8]',
+ output: 'Array [ 0, <2 empty slots>, 3, <3 empty slots>, 7, 8 ]',
+ printOutput: "0,,,3,,,,7,8",
+ inspectable: true,
+ variablesViewLabel: "Array[9]",
+ },
+ // 8 - array with a single slot hole at the end
+ {
+ input: '[0,1,2,3,4,,]',
+ output: 'Array [ 0, 1, 2, 3, 4, <1 empty slot> ]',
+ printOutput: "0,1,2,3,4,",
+ inspectable: true,
+ variablesViewLabel: "Array[6]",
+ },
+ // 9 - array with multiple consecutive empty slots at the end
+ {
+ input: '[0,1,2,,,,]',
+ output: 'Array [ 0, 1, 2, <3 empty slots> ]',
+ printOutput: "0,1,2,,,",
+ inspectable: true,
+ variablesViewLabel: "Array[6]",
+ },
+
+ // 10 - array with members explicitly set to null
+ {
+ input: '[0,null,null,3,4,5]',
+ output: 'Array [ 0, null, null, 3, 4, 5 ]',
+ printOutput: "0,,,3,4,5",
+ inspectable: true,
+ variablesViewLabel: "Array[6]"
+ },
+
+ // 11 - array with members explicitly set to undefined
+ {
+ input: '[0,undefined,undefined,3,4,5]',
+ output: 'Array [ 0, undefined, undefined, 3, 4, 5 ]',
+ printOutput: "0,,,3,4,5",
+ inspectable: true,
+ variablesViewLabel: "Array[6]"
+ },
+
+ //12 - array with long strings as elements
+ {
+ input: '["' + test_str_in + '", "' + test_str_in + '", "' + test_str_in + '"]',
+ output: 'Array [ "' + test_str_out + '", "' + test_str_out + '", "' + test_str_out + '" ]',
+ inspectable: false,
+ printOutput: "SHOW\nALL\nOF\nTHIS\nON\nA\nSINGLE\nLINE ONLY. ESCAPE ALL NEWLINE,SHOW\nALL\nOF\nTHIS\nON\nA\nSINGLE\nLINE ONLY. ESCAPE ALL NEWLINE,SHOW\nALL\nOF\nTHIS\nON\nA\nSINGLE\nLINE ONLY. ESCAPE ALL NEWLINE",
+ variablesViewLabel: "Array[3]"
+ }
+];
+
+function test() {
+ requestLongerTimeout(2);
+ Task.spawn(function*() {
+ let {tab} = yield loadTab(TEST_URI);
+ let hud = yield openConsole(tab);
+ return checkOutputForInputs(hud, inputTests);
+ }).then(finishUp);
+}
+
+function finishUp() {
+ inputTests = null;
+ finishTest();
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_copy_newlines.js b/browser/devtools/webconsole/test/browser_webconsole_output_copy_newlines.js
new file mode 100644
index 000000000..376099661
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_copy_newlines.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that multiple messages are copied into the clipboard and that they are
+// separated by new lines. See bug 916997.
+
+"use strict";
+
+let test = asyncTest(function*() {
+ const TEST_URI = "data:text/html;charset=utf8,<p>hello world, bug 916997";
+ let clipboardValue = "";
+
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+ hud.jsterm.clearOutput();
+
+ let controller = top.document.commandDispatcher.
+ getControllerForCommand("cmd_copy");
+ is(controller.isCommandEnabled("cmd_copy"), false, "cmd_copy is disabled");
+
+ content.console.log("Hello world! bug916997a");
+ content.console.log("Hello world 2! bug916997b");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "Hello world! bug916997a",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }, {
+ text: "Hello world 2! bug916997b",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ hud.ui.output.selectAllMessages();
+ hud.outputNode.focus();
+
+ goUpdateCommand("cmd_copy");
+ controller = top.document.commandDispatcher.getControllerForCommand("cmd_copy");
+ is(controller.isCommandEnabled("cmd_copy"), true, "cmd_copy is enabled");
+
+ let selection = hud.iframeWindow.getSelection() + "";
+ info("selection '" + selection + "'");
+
+ waitForClipboard((str) => {
+ clipboardValue = str;
+ return str.indexOf("bug916997a") > -1 && str.indexOf("bug916997b") > -1;
+ },
+ () => { goDoCommand("cmd_copy"); },
+ () => {
+ info("clipboard value '" + clipboardValue + "'");
+ let lines = clipboardValue.trim().split("\n");
+ is(hud.outputNode.children.length, 2, "number of messages");
+ is(lines.length, hud.outputNode.children.length, "number of lines");
+ isnot(lines[0].indexOf("bug916997a"), -1,
+ "first message text includes 'bug916997a'");
+ isnot(lines[1].indexOf("bug916997b"), -1,
+ "second message text includes 'bug916997b'");
+ is(lines[0].indexOf("bug916997b"), -1,
+ "first message text does not include 'bug916997b'");
+ },
+ () => {
+ info("last clipboard value: '" + clipboardValue + "'");
+ });
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_01.js b/browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_01.js
new file mode 100644
index 000000000..e7cc49369
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_01.js
@@ -0,0 +1,108 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejections should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed(null);
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: this.toolbox is null");
+
+// Test the webconsole output for various types of DOM Nodes.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-dom-elements.html";
+
+let inputTests = [
+ {
+ input: "testBodyNode()",
+ output: '<body id="body-id" class="body-class">',
+ printOutput: "[object HTMLBodyElement]",
+ inspectable: true,
+ noClick: true,
+ inspectorIcon: true
+ },
+
+ {
+ input: "testDocumentElement()",
+ output: '<html lang="en-US" dir="ltr">',
+ printOutput: "[object HTMLHtmlElement]",
+ inspectable: true,
+ noClick: true,
+ inspectorIcon: true
+ },
+
+ {
+ input: "testDocument()",
+ output: 'HTMLDocument \u2192 ' + TEST_URI,
+ printOutput: "[object HTMLDocument]",
+ inspectable: true,
+ noClick: true,
+ inspectorIcon: false
+ },
+
+ {
+ input: "testNode()",
+ output: '<p some-attribute="some-value">',
+ printOutput: "[object HTMLParagraphElement]",
+ inspectable: true,
+ noClick: true,
+ inspectorIcon: true
+ },
+
+ {
+ input: "testNodeList()",
+ output: 'NodeList [ <html>, <head>, <meta>, <title>, <body#body-id.body-class>, <p>, <iframe>, <div.some.classname.here.with.more.classnames.here>, <script> ]',
+ printOutput: "[object NodeList]",
+ inspectable: true,
+ noClick: true,
+ inspectorIcon: true
+ },
+
+ {
+ input: "testNodeInIframe()",
+ output: '<p>',
+ printOutput: "[object HTMLParagraphElement]",
+ inspectable: true,
+ noClick: true,
+ inspectorIcon: true
+ },
+
+ {
+ input: "testDocumentFragment()",
+ output: 'DocumentFragment [ <span.foo>, <div#fragdiv> ]',
+ printOutput: "[object DocumentFragment]",
+ inspectable: true,
+ noClick: true,
+ inspectorIcon: false
+ },
+
+ {
+ input: "testNodeInDocumentFragment()",
+ output: '<span class="foo" data-lolz="hehe">',
+ printOutput: "[object HTMLSpanElement]",
+ inspectable: true,
+ noClick: true,
+ inspectorIcon: false
+ },
+
+ {
+ input: "testUnattachedNode()",
+ output: '<p class="such-class" data-data="such-data">',
+ printOutput: "[object HTMLParagraphElement]",
+ inspectable: true,
+ noClick: true,
+ inspectorIcon: false
+ }
+];
+
+function test() {
+ requestLongerTimeout(2);
+ Task.spawn(function*() {
+ let {tab} = yield loadTab(TEST_URI);
+ let hud = yield openConsole(tab);
+ yield checkOutputForInputs(hud, inputTests);
+ }).then(finishTest);
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_02.js b/browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_02.js
new file mode 100644
index 000000000..d6db04ea1
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_02.js
@@ -0,0 +1,111 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test the inspector links in the webconsole output for DOM Nodes actually
+// open the inspector and select the right node
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-dom-elements.html";
+
+const TEST_DATA = [
+ {
+ // The first test shouldn't be returning the body element as this is the
+ // default selected node, so re-selecting it won't fire the inspector-updated
+ // event
+ input: "testNode()",
+ output: '<p some-attribute="some-value">',
+ tagName: 'P',
+ attrs: [{name: "some-attribute", value: "some-value"}]
+ },
+ {
+ input: "testBodyNode()",
+ output: '<body id="body-id" class="body-class">',
+ tagName: 'BODY',
+ attrs: [{name: "id", value: "body-id"}, {name: "class", value: "body-class"}]
+ },
+ {
+ input: "testNodeInIframe()",
+ output: '<p>',
+ tagName: 'P',
+ attrs: []
+ },
+ {
+ input: "testDocumentElement()",
+ output: '<html lang="en-US" dir="ltr">',
+ tagName: 'HTML',
+ attrs: [{name: "lang", value: "en-US"}, {name: "dir", value: "ltr"}]
+ }
+];
+
+function test() {
+ Task.spawn(function*() {
+ let {tab} = yield loadTab(TEST_URI);
+ let hud = yield openConsole(tab);
+ let toolbox = gDevTools.getToolbox(hud.target);
+
+ // Loading the inspector panel at first, to make it possible to listen for
+ // new node selections
+ yield toolbox.selectTool("inspector");
+ let inspector = toolbox.getCurrentPanel();
+ yield toolbox.selectTool("webconsole");
+
+ info("Iterating over the test data");
+ for (let data of TEST_DATA) {
+ let [result] = yield jsEval(data.input, hud, {text: data.output});
+ let {widget, msg} = yield getWidgetAndMessage(result);
+
+ let inspectorIcon = msg.querySelector(".open-inspector");
+ ok(inspectorIcon, "Inspector icon found in the ElementNode widget");
+
+ info("Clicking on the inspector icon and waiting for the inspector to be selected");
+ let onInspectorSelected = toolbox.once("inspector-selected");
+ let onInspectorUpdated = inspector.once("inspector-updated");
+ let onNewNode = toolbox.selection.once("new-node-front");
+
+ EventUtils.synthesizeMouseAtCenter(inspectorIcon, {},
+ inspectorIcon.ownerDocument.defaultView);
+ yield onInspectorSelected;
+ yield onInspectorUpdated;
+ let nodeFront = yield onNewNode;
+
+ ok(true, "Inspector selected and new node got selected");
+
+ is(nodeFront.tagName, data.tagName, "The correct node was highlighted");
+
+ let attrs = nodeFront.attributes;
+ for (let i in data.attrs) {
+ is(attrs[i].name, data.attrs[i].name, "The correct node was highlighted");
+ is(attrs[i].value, data.attrs[i].value, "The correct node was highlighted");
+ }
+
+ info("Switching back to the console");
+ yield toolbox.selectTool("webconsole");
+ }
+ }).then(finishTest);
+}
+
+function jsEval(input, hud, message) {
+ info("Executing '" + input + "' in the web console");
+
+ hud.jsterm.clearOutput();
+ hud.jsterm.execute(input);
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [message]
+ });
+}
+
+function* getWidgetAndMessage(result) {
+ info("Getting the output ElementNode widget");
+
+ let msg = [...result.matched][0];
+ let widget = [...msg._messageObject.widgets][0];
+ ok(widget, "ElementNode widget found in the output");
+
+ info("Waiting for the ElementNode widget to be linked to the inspector");
+ yield widget.linkToInspector();
+
+ return {widget: widget, msg: msg};
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_03.js b/browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_03.js
new file mode 100644
index 000000000..cc420d492
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_03.js
@@ -0,0 +1,67 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that inspector links in webconsole outputs for DOM Nodes highlight
+// the actual DOM Nodes on hover
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-dom-elements.html";
+
+function test() {
+ Task.spawn(function*() {
+ let {tab} = yield loadTab(TEST_URI);
+ let hud = yield openConsole(tab);
+ let toolbox = gDevTools.getToolbox(hud.target);
+
+ // Loading the inspector panel at first, to make it possible to listen for
+ // new node selections
+ yield toolbox.loadTool("inspector");
+ let inspector = toolbox.getPanel("inspector");
+
+ info("Executing 'testNode()' in the web console to output a DOM Node");
+ let [result] = yield jsEval("testNode()", hud, {
+ text: '<p some-attribute="some-value">'
+ });
+
+ let elementNodeWidget = yield getWidget(result);
+
+ let nodeFront = yield hoverOverWidget(elementNodeWidget, toolbox);
+ let attrs = nodeFront.attributes;
+ is(nodeFront.tagName, "P", "The correct node was highlighted");
+ is(attrs[0].name, "some-attribute", "The correct node was highlighted");
+ is(attrs[0].value, "some-value", "The correct node was highlighted");
+ }).then(finishTest);
+}
+
+function jsEval(input, hud, message) {
+ hud.jsterm.execute(input);
+ return waitForMessages({
+ webconsole: hud,
+ messages: [message]
+ });
+}
+
+function* getWidget(result) {
+ info("Getting the output ElementNode widget");
+
+ let msg = [...result.matched][0];
+ let elementNodeWidget = [...msg._messageObject.widgets][0];
+ ok(elementNodeWidget, "ElementNode widget found in the output");
+
+ info("Waiting for the ElementNode widget to be linked to the inspector");
+ yield elementNodeWidget.linkToInspector();
+
+ return elementNodeWidget;
+}
+
+function* hoverOverWidget(widget, toolbox) {
+ info("Hovering over the output to highlight the node");
+
+ let onHighlight = toolbox.once("node-highlight");
+ EventUtils.sendMouseEvent({type: "mouseover"}, widget.element,
+ widget.element.ownerDocument.defaultView);
+ let nodeFront = yield onHighlight;
+ ok(true, "The highlighter was shown on a node");
+ return nodeFront;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_04.js b/browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_04.js
new file mode 100644
index 000000000..7cb0847e7
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_dom_elements_04.js
@@ -0,0 +1,106 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test that inspector links in the webconsole output for DOM Nodes do not try
+// to highlight or select nodes once they have been detached
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-dom-elements.html";
+
+const TEST_DATA = [
+ {
+ // The first test shouldn't be returning the body element as this is the
+ // default selected node, so re-selecting it won't fire the inspector-updated
+ // event
+ input: "testNode()",
+ output: '<p some-attribute="some-value">'
+ },
+ {
+ input: "testBodyNode()",
+ output: '<body id="body-id" class="body-class">'
+ },
+ {
+ input: "testNodeInIframe()",
+ output: '<p>'
+ },
+ {
+ input: "testDocumentElement()",
+ output: '<html lang="en-US" dir="ltr">'
+ }
+];
+
+const PREF = "devtools.webconsole.persistlog";
+
+function test() {
+ Services.prefs.setBoolPref(PREF, true);
+ registerCleanupFunction(() => Services.prefs.clearUserPref(PREF));
+
+ Task.spawn(function*() {
+ let {tab} = yield loadTab(TEST_URI);
+ let hud = yield openConsole(tab);
+ let toolbox = gDevTools.getToolbox(hud.target);
+
+ info("Executing the test data");
+ let widgets = [];
+ for (let data of TEST_DATA) {
+ let [result] = yield jsEval(data.input, hud, {text: data.output});
+ let {widget} = yield getWidgetAndMessage(result);
+ widgets.push(widget);
+ }
+
+ info("Reloading the page");
+ yield reloadPage();
+
+ info("Iterating over the ElementNode widgets");
+ for (let widget of widgets) {
+ // Verify that openNodeInInspector rejects since the associated dom node
+ // doesn't exist anymore
+ yield widget.openNodeInInspector().then(() => {
+ ok(false, "The openNodeInInspector promise resolved");
+ }, () => {
+ ok(true, "The openNodeInInspector promise rejected as expected");
+ });
+ yield toolbox.selectTool("webconsole");
+
+ // Verify that highlightDomNode rejects too, for the same reason
+ yield widget.highlightDomNode().then(() => {
+ ok(false, "The highlightDomNode promise resolved");
+ }, () => {
+ ok(true, "The highlightDomNode promise rejected as expected");
+ });
+ }
+ }).then(finishTest);
+}
+
+function jsEval(input, hud, message) {
+ info("Executing '" + input + "' in the web console");
+ hud.jsterm.execute(input);
+ return waitForMessages({
+ webconsole: hud,
+ messages: [message]
+ });
+}
+
+function* getWidgetAndMessage(result) {
+ info("Getting the output ElementNode widget");
+
+ let msg = [...result.matched][0];
+ let widget = [...msg._messageObject.widgets][0];
+ ok(widget, "ElementNode widget found in the output");
+
+ info("Waiting for the ElementNode widget to be linked to the inspector");
+ yield widget.linkToInspector();
+
+ return {widget: widget, msg: msg};
+}
+
+function reloadPage() {
+ let def = promise.defer();
+ gBrowser.selectedBrowser.addEventListener("load", function onload() {
+ gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+ def.resolve();
+ }, true);
+ content.location.reload();
+ return def.promise;
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_events.js b/browser/devtools/webconsole/test/browser_webconsole_output_events.js
new file mode 100644
index 000000000..72b2ec93d
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_events.js
@@ -0,0 +1,53 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("null");
+
+// Test the webconsole output for DOM events.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-events.html";
+
+let test = asyncTest(function* () {
+ yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ hud.jsterm.clearOutput();
+ hud.jsterm.execute("testDOMEvents()");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "testDOMEvents() output",
+ text: "undefined",
+ category: CATEGORY_OUTPUT,
+ }],
+ });
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "console.log() output for mousemove",
+ text: /"eventLogger" mousemove { target: .+, buttons: 0, clientX: \d+, clientY: \d+, layerX: \d+, layerY: \d+ }/,
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "console.log() output for keypress",
+ text: /"eventLogger" keypress Shift { target: .+, key: .+, charCode: \d+, keyCode: \d+ }/,
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+}); \ No newline at end of file
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_order.js b/browser/devtools/webconsole/test/browser_webconsole_output_order.js
new file mode 100644
index 000000000..4a2a9f6be
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_order.js
@@ -0,0 +1,47 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests that any output created from calls to the console API comes after the
+// echoed JavaScript.
+
+"use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console.html";
+
+let test = asyncTest(function*() {
+ yield loadTab(TEST_URI);
+ let hud = yield openConsole();
+
+ let jsterm = hud.jsterm;
+ let outputNode = jsterm.outputNode;
+
+ jsterm.clearOutput();
+ jsterm.execute("console.log('foo', 'bar');");
+
+ let [function_call, result, console_message] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "console.log('foo', 'bar');",
+ category: CATEGORY_INPUT,
+ },
+ {
+ text: "undefined",
+ category: CATEGORY_OUTPUT,
+ },
+ {
+ text: '"foo" "bar"',
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ let fncall_node = [...function_call.matched][0];
+ let result_node = [...result.matched][0];
+ let console_message_node = [...console_message.matched][0];
+ is(fncall_node.nextElementSibling, result_node,
+ "console.log() is followed by undefined");
+ is(result_node.nextElementSibling, console_message_node,
+ "undefined is followed by 'foo' 'bar'");
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_output_table.js b/browser/devtools/webconsole/test/browser_webconsole_output_table.js
new file mode 100644
index 000000000..512a3bc8f
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_table.js
@@ -0,0 +1,158 @@
+ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+ /* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that console.table() works as intended.
+
+ "use strict";
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-table.html";
+
+const TEST_DATA = [
+ {
+ command: "console.table(languages1)",
+ data: [
+ { _index: 0, name: "\"JavaScript\"", fileExtension: "Array[1]" },
+ { _index: 1, name: "Object", fileExtension: "\".ts\"" },
+ { _index: 2, name: "\"CoffeeScript\"", fileExtension: "\".coffee\"" }
+ ],
+ columns: { _index: "(index)", name: "name", fileExtension: "fileExtension" }
+ },
+ {
+ command: "console.table(languages1, 'name')",
+ data: [
+ { _index: 0, name: "\"JavaScript\"", fileExtension: "Array[1]" },
+ { _index: 1, name: "Object", fileExtension: "\".ts\"" },
+ { _index: 2, name: "\"CoffeeScript\"", fileExtension: "\".coffee\"" }
+ ],
+ columns: { _index: "(index)", name: "name" }
+ },
+ {
+ command: "console.table(languages1, ['name'])",
+ data: [
+ { _index: 0, name: "\"JavaScript\"", fileExtension: "Array[1]" },
+ { _index: 1, name: "Object", fileExtension: "\".ts\"" },
+ { _index: 2, name: "\"CoffeeScript\"", fileExtension: "\".coffee\"" }
+ ],
+ columns: { _index: "(index)", name: "name" }
+ },
+ {
+ command: "console.table(languages2)",
+ data: [
+ { _index: "csharp", name: "\"C#\"", paradigm: "\"object-oriented\"" },
+ { _index: "fsharp", name: "\"F#\"", paradigm: "\"functional\"" }
+ ],
+ columns: { _index: "(index)", name: "name", paradigm: "paradigm" }
+ },
+ {
+ command: "console.table([[1, 2], [3, 4]])",
+ data: [
+ { _index: 0, 0: "1", 1: "2" },
+ { _index: 1, 0: "3", 1: "4" }
+ ],
+ columns: { _index: "(index)", 0: "0", 1: "1" }
+ },
+ {
+ command: "console.table({a: [1, 2], b: [3, 4]})",
+ data: [
+ { _index: "a", 0: "1", 1: "2" },
+ { _index: "b", 0: "3", 1: "4" }
+ ],
+ columns: { _index: "(index)", 0: "0", 1: "1" }
+ },
+ {
+ command: "console.table(family)",
+ data: [
+ { _index: "mother", firstName: "\"Susan\"", lastName: "\"Doyle\"", age: "32" },
+ { _index: "father", firstName: "\"John\"", lastName: "\"Doyle\"", age: "33" },
+ { _index: "daughter", firstName: "\"Lily\"", lastName: "\"Doyle\"", age: "5" },
+ { _index: "son", firstName: "\"Mike\"", lastName: "\"Doyle\"", age: "8" },
+ ],
+ columns: { _index: "(index)", firstName: "firstName", lastName: "lastName", age: "age" }
+ },
+ {
+ command: "console.table(family, [])",
+ data: [
+ { _index: "mother", firstName: "\"Susan\"", lastName: "\"Doyle\"", age: "32" },
+ { _index: "father", firstName: "\"John\"", lastName: "\"Doyle\"", age: "33" },
+ { _index: "daughter", firstName: "\"Lily\"", lastName: "\"Doyle\"", age: "5" },
+ { _index: "son", firstName: "\"Mike\"", lastName: "\"Doyle\"", age: "8" },
+ ],
+ columns: { _index: "(index)" }
+ },
+ {
+ command: "console.table(family, ['firstName', 'lastName'])",
+ data: [
+ { _index: "mother", firstName: "\"Susan\"", lastName: "\"Doyle\"", age: "32" },
+ { _index: "father", firstName: "\"John\"", lastName: "\"Doyle\"", age: "33" },
+ { _index: "daughter", firstName: "\"Lily\"", lastName: "\"Doyle\"", age: "5" },
+ { _index: "son", firstName: "\"Mike\"", lastName: "\"Doyle\"", age: "8" },
+ ],
+ columns: { _index: "(index)", firstName: "firstName", lastName: "lastName" }
+ },
+ {
+ command: "console.table(mySet)",
+ data: [
+ { _index: 0, _value: "1" },
+ { _index: 1, _value: "5" },
+ { _index: 2, _value: "\"some text\"" },
+ { _index: 3, _value: "null" },
+ { _index: 4, _value: "undefined" }
+ ],
+ columns: { _index: "(iteration index)", _value: "Values" }
+ },
+ {
+ command: "console.table(myMap)",
+ data: [
+ { _index: 0, _key: "\"a string\"", _value: "\"value associated with 'a string'\"" },
+ { _index: 1, _key: "5", _value: "\"value associated with 5\"" },
+ ],
+ columns: { _index: "(iteration index)", _key: "Key", _value: "Values" }
+ }
+];
+
+add_task(function*() {
+ const {tab} = yield loadTab(TEST_URI);
+ let hud = yield openConsole(tab);
+
+ for (let testdata of TEST_DATA) {
+ hud.jsterm.clearOutput();
+
+ info("Executing " + testdata.command);
+
+ let onTableRender = once(hud.ui, "messages-table-rendered");
+ hud.jsterm.execute(testdata.command);
+ yield onTableRender;
+
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: testdata.command + " output",
+ consoleTable: true
+ }],
+ });
+
+ let node = [...result.matched][0];
+ ok(node, "found trace log node");
+
+ let obj = node._messageObject;
+ ok(obj, "console.trace message object");
+
+ ok(obj._data, "found table data object");
+
+ let data = obj._data.map(entries => {
+ let result = {};
+
+ for (let key of Object.keys(entries)) {
+ result[key] = entries[key] instanceof HTMLElement ?
+ entries[key].textContent : entries[key];
+ }
+
+ return result;
+ });
+
+ is(data.toSource(), testdata.data.toSource(), "table data is correct");
+ ok(obj._columns, "found table column object");
+ is(obj._columns.toSource(), testdata.columns.toSource(), "table column is correct");
+ }
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_property_provider.js b/browser/devtools/webconsole/test/browser_webconsole_property_provider.js
new file mode 100644
index 000000000..14c054d36
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_property_provider.js
@@ -0,0 +1,45 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+// Tests the property provider, which is part of the code completion
+// infrastructure.
+
+const TEST_URI = "data:text/html;charset=utf8,<p>test the JS property provider";
+
+function test() {
+ loadTab(TEST_URI).then(testPropertyProvider);
+}
+
+function testPropertyProvider({browser}) {
+ browser.removeEventListener("load", testPropertyProvider, true);
+ let tools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+ let JSPropertyProvider = tools.require("devtools/toolkit/webconsole/utils").JSPropertyProvider;
+
+ let tmp = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
+ tmp.addDebuggerToGlobal(tmp);
+ let dbg = new tmp.Debugger;
+ let dbgWindow = dbg.makeGlobalObjectReference(content);
+
+ let completion = JSPropertyProvider(dbgWindow, null, "thisIsNotDefined");
+ is (completion.matches.length, 0, "no match for 'thisIsNotDefined");
+
+ // This is a case the PropertyProvider can't handle. Should return null.
+ completion = JSPropertyProvider(dbgWindow, null, "window[1].acb");
+ is (completion, null, "no match for 'window[1].acb");
+
+ // A very advanced completion case.
+ var strComplete =
+ 'function a() { }document;document.getElementById(window.locatio';
+ completion = JSPropertyProvider(dbgWindow, null, strComplete);
+ ok(completion.matches.length == 2, "two matches found");
+ ok(completion.matchProp == "locatio", "matching part is 'test'");
+ var matches = completion.matches;
+ matches.sort();
+ ok(matches[0] == "location", "the first match is 'location'");
+ ok(matches[1] == "locationbar", "the second match is 'locationbar'");
+
+ finishTest();
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_reflow.js b/browser/devtools/webconsole/test/browser_webconsole_reflow.js
new file mode 100644
index 000000000..328b296de
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_reflow.js
@@ -0,0 +1,32 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for reflow activity";
+
+let test = asyncTest(function* () {
+ let { browser } = yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ function onReflowListenersReady(aType, aPacket) {
+ browser.contentDocument.body.style.display = "none";
+ browser.contentDocument.body.clientTop;
+ }
+
+ Services.prefs.setBoolPref("devtools.webconsole.filter.csslog", true);
+ hud.ui._updateReflowActivityListener(onReflowListenersReady);
+ Services.prefs.clearUserPref("devtools.webconsole.filter.csslog");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: /reflow: /,
+ category: CATEGORY_CSS,
+ severity: SEVERITY_LOG,
+ }],
+ })
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_scratchpad_panel_link.js b/browser/devtools/webconsole/test/browser_webconsole_scratchpad_panel_link.js
new file mode 100644
index 000000000..c0899d868
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_scratchpad_panel_link.js
@@ -0,0 +1,63 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_URI = "data:text/html;charset=utf8,<p>test Scratchpad panel linking</p>";
+
+let { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
+let { Tools } = require("main");
+let { isTargetSupported } = Tools.scratchpad;
+
+Tools.scratchpad.isTargetSupported = () => true;
+
+add_task(function*() {
+ waitForExplicitFinish();
+ yield loadTab(TEST_URI);
+
+ info("Opening toolbox with Scratchpad panel");
+
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ let toolbox = yield gDevTools.showToolbox(target, "scratchpad", "window");
+
+ let scratchpadPanel = toolbox.getPanel("scratchpad");
+ let { scratchpad } = scratchpadPanel;
+ is(toolbox.getCurrentPanel(), scratchpadPanel,
+ "Scratchpad is currently selected panel");
+
+ info("Switching to webconsole panel");
+
+ let webconsolePanel = yield toolbox.selectTool("webconsole");
+ let { hud } = webconsolePanel;
+ is(toolbox.getCurrentPanel(), webconsolePanel,
+ "Webconsole is currently selected panel");
+
+ info("console.log()ing from Scratchpad");
+
+ scratchpad.setText("console.log('foobar-from-scratchpad')");
+ scratchpad.run();
+ let messages = yield waitForMessages({
+ webconsole: hud,
+ messages: [{ text: "foobar-from-scratchpad" }]
+ });
+
+ info("Clicking link to switch to and focus Scratchpad");
+
+ let [matched] = [...messages[0].matched];
+ ok(matched, "Found logged message from Scratchpad");
+ let anchor = matched.querySelector("a.message-location");
+
+ toolbox.on("scratchpad-selected", function selected() {
+ toolbox.off("scratchpad-selected", selected);
+
+ is(toolbox.getCurrentPanel(), scratchpadPanel,
+ "Clicking link switches to Scratchpad panel");
+
+ is(Services.ww.activeWindow, toolbox.frame.ownerGlobal,
+ "Scratchpad's toolbox is focused");
+
+ Tools.scratchpad.isTargetSupported = isTargetSupported;
+ finish();
+ });
+
+ EventUtils.synthesizeMouse(anchor, 2, 2, {}, hud.iframeWindow);
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_show_subresource_security_errors.js b/browser/devtools/webconsole/test/browser_webconsole_show_subresource_security_errors.js
new file mode 100644
index 000000000..282c8aab1
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_show_subresource_security_errors.js
@@ -0,0 +1,30 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Ensure non-toplevel security errors are displayed
+
+const TEST_URI = "data:text/html;charset=utf8,Web Console subresource STS warning test";
+const TEST_DOC = "https://example.com/browser/browser/devtools/webconsole/test/test_bug1092055_shouldwarn.html";
+const SAMPLE_MSG = 'invalid Strict-Transport-Security header'
+
+let test = asyncTest(function* () {
+ let { browser } = yield loadTab(TEST_URI);
+
+ let hud = yield openConsole();
+
+ hud.jsterm.clearOutput();
+
+ let loaded = loadBrowser(browser);
+ content.location = TEST_DOC;
+ yield loaded;
+
+ yield waitForSuccess({
+ name: "Subresource STS warning displayed successfully",
+ validator: function() {
+ return hud.outputNode.textContent.indexOf(SAMPLE_MSG) > -1;
+ }
+ });
+});
diff --git a/browser/devtools/webconsole/test/browser_webconsole_split.js b/browser/devtools/webconsole/test/browser_webconsole_split.js
new file mode 100644
index 000000000..9501cd238
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_split.js
@@ -0,0 +1,247 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for splitting";
+
+function test()
+{
+ // Test is slow on Linux EC2 instances - Bug 962931
+ requestLongerTimeout(2);
+
+ let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+ let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+ let Toolbox = devtools.Toolbox;
+ let toolbox;
+
+ loadTab(TEST_URI).then(testConsoleLoadOnDifferentPanel);
+
+ function testConsoleLoadOnDifferentPanel()
+ {
+ info("About to check console loads even when non-webconsole panel is open");
+
+ openPanel("inspector").then(() => {
+ toolbox.on("webconsole-ready", () => {
+ ok(true, "Webconsole has been triggered as loaded while another tool is active");
+ testKeyboardShortcuts();
+ });
+
+ // Opens split console.
+ toolbox.toggleSplitConsole();
+ });
+ }
+
+ function testKeyboardShortcuts()
+ {
+ info("About to check that panel responds to ESCAPE keyboard shortcut");
+
+ toolbox.once("split-console", () => {
+ ok(true, "Split console has been triggered via ESCAPE keypress");
+ checkAllTools();
+ });
+
+ // Closes split console.
+ EventUtils.sendKey("ESCAPE", toolbox.frame.contentWindow);
+ }
+
+ function checkAllTools()
+ {
+ info("About to check split console with each panel individually.");
+
+ Task.spawn(function() {
+ yield openAndCheckPanel("jsdebugger");
+ yield openAndCheckPanel("inspector");
+ yield openAndCheckPanel("styleeditor");
+ yield openAndCheckPanel("jsprofiler");
+ yield openAndCheckPanel("netmonitor");
+
+ yield checkWebconsolePanelOpened();
+ testBottomHost();
+ });
+ }
+
+ function getCurrentUIState()
+ {
+ let win = toolbox.doc.defaultView;
+ let deck = toolbox.doc.querySelector("#toolbox-deck");
+ let webconsolePanel = toolbox.webconsolePanel;
+ let splitter = toolbox.doc.querySelector("#toolbox-console-splitter");
+
+ let containerHeight = parseFloat(win.getComputedStyle(deck.parentNode).getPropertyValue("height"));
+ let deckHeight = parseFloat(win.getComputedStyle(deck).getPropertyValue("height"));
+ let webconsoleHeight = parseFloat(win.getComputedStyle(webconsolePanel).getPropertyValue("height"));
+ let splitterVisibility = !splitter.getAttribute("hidden");
+ let openedConsolePanel = toolbox.currentToolId === "webconsole";
+ let cmdButton = toolbox.doc.querySelector("#command-button-splitconsole");
+
+ return {
+ deckHeight: deckHeight,
+ containerHeight: containerHeight,
+ webconsoleHeight: webconsoleHeight,
+ splitterVisibility: splitterVisibility,
+ openedConsolePanel: openedConsolePanel,
+ buttonSelected: cmdButton.hasAttribute("checked")
+ };
+ }
+
+ function checkWebconsolePanelOpened()
+ {
+ info("About to check special cases when webconsole panel is open.");
+
+ let deferred = promise.defer();
+
+ // Start with console split, so we can test for transition to main panel.
+ toolbox.toggleSplitConsole();
+
+ let currentUIState = getCurrentUIState();
+
+ ok (currentUIState.splitterVisibility, "Splitter is visible when console is split");
+ ok (currentUIState.deckHeight > 0, "Deck has a height > 0 when console is split");
+ ok (currentUIState.webconsoleHeight > 0, "Web console has a height > 0 when console is split");
+ ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
+ ok (currentUIState.buttonSelected, "The command button is selected");
+
+ openPanel("webconsole").then(() => {
+
+ let currentUIState = getCurrentUIState();
+
+ ok (!currentUIState.splitterVisibility, "Splitter is hidden when console is opened.");
+ is (currentUIState.deckHeight, 0, "Deck has a height == 0 when console is opened.");
+ is (currentUIState.webconsoleHeight, currentUIState.containerHeight, "Web console is full height.");
+ ok (currentUIState.openedConsolePanel, "The console panel is the current tool");
+ ok (currentUIState.buttonSelected, "The command button is still selected.");
+
+ // Make sure splitting console does nothing while webconsole is opened
+ toolbox.toggleSplitConsole();
+
+ currentUIState = getCurrentUIState();
+
+ ok (!currentUIState.splitterVisibility, "Splitter is hidden when console is opened.");
+ is (currentUIState.deckHeight, 0, "Deck has a height == 0 when console is opened.");
+ is (currentUIState.webconsoleHeight, currentUIState.containerHeight, "Web console is full height.");
+ ok (currentUIState.openedConsolePanel, "The console panel is the current tool");
+ ok (currentUIState.buttonSelected, "The command button is still selected.");
+
+ // Make sure that split state is saved after opening another panel
+ openPanel("inspector").then(() => {
+ let currentUIState = getCurrentUIState();
+ ok (currentUIState.splitterVisibility, "Splitter is visible when console is split");
+ ok (currentUIState.deckHeight > 0, "Deck has a height > 0 when console is split");
+ ok (currentUIState.webconsoleHeight > 0, "Web console has a height > 0 when console is split");
+ ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
+ ok (currentUIState.buttonSelected, "The command button is still selected.");
+
+ toolbox.toggleSplitConsole();
+ deferred.resolve();
+
+ });
+ });
+ return deferred.promise;
+ }
+
+ function openPanel(toolId, callback)
+ {
+ let deferred = promise.defer();
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ gDevTools.showToolbox(target, toolId).then(function(box) {
+ toolbox = box;
+ deferred.resolve();
+ }).then(null, console.error);
+ return deferred.promise;
+ }
+
+ function openAndCheckPanel(toolId)
+ {
+ let deferred = promise.defer();
+ openPanel(toolId).then(() => {
+ info ("Checking toolbox for " + toolId);
+ checkToolboxUI(toolbox.getCurrentPanel());
+ deferred.resolve();
+ });
+ return deferred.promise;
+ }
+
+ function checkToolboxUI()
+ {
+ let currentUIState = getCurrentUIState();
+
+ ok (!currentUIState.splitterVisibility, "Splitter is hidden by default");
+ is (currentUIState.deckHeight, currentUIState.containerHeight, "Deck has a height > 0 by default");
+ is (currentUIState.webconsoleHeight, 0, "Web console is collapsed by default");
+ ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
+ ok (!currentUIState.buttonSelected, "The command button is not selected.");
+
+ toolbox.toggleSplitConsole();
+
+ currentUIState = getCurrentUIState();
+
+ ok (currentUIState.splitterVisibility, "Splitter is visible when console is split");
+ ok (currentUIState.deckHeight > 0, "Deck has a height > 0 when console is split");
+ ok (currentUIState.webconsoleHeight > 0, "Web console has a height > 0 when console is split");
+ is (Math.round(currentUIState.deckHeight + currentUIState.webconsoleHeight),
+ currentUIState.containerHeight,
+ "Everything adds up to container height");
+ ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
+ ok (currentUIState.buttonSelected, "The command button is selected.");
+
+ toolbox.toggleSplitConsole();
+
+ currentUIState = getCurrentUIState();
+
+ ok (!currentUIState.splitterVisibility, "Splitter is hidden after toggling");
+ is (currentUIState.deckHeight, currentUIState.containerHeight, "Deck has a height > 0 after toggling");
+ is (currentUIState.webconsoleHeight, 0, "Web console is collapsed after toggling");
+ ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
+ ok (!currentUIState.buttonSelected, "The command button is not selected.");
+ }
+
+ function testBottomHost()
+ {
+ checkHostType(Toolbox.HostType.BOTTOM);
+
+ checkToolboxUI();
+
+ toolbox.switchHost(Toolbox.HostType.SIDE).then(testSidebarHost);
+ }
+
+ function testSidebarHost()
+ {
+ checkHostType(Toolbox.HostType.SIDE);
+
+ checkToolboxUI();
+
+ toolbox.switchHost(Toolbox.HostType.WINDOW).then(testWindowHost);
+ }
+
+ function testWindowHost()
+ {
+ checkHostType(Toolbox.HostType.WINDOW);
+
+ checkToolboxUI();
+
+ toolbox.switchHost(Toolbox.HostType.BOTTOM).then(testDestroy);
+ }
+
+ function checkHostType(hostType)
+ {
+ is(toolbox.hostType, hostType, "host type is " + hostType);
+
+ let pref = Services.prefs.getCharPref("devtools.toolbox.host");
+ is(pref, hostType, "host pref is " + hostType);
+ }
+
+ function testDestroy()
+ {
+ toolbox.destroy().then(function() {
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ gDevTools.showToolbox(target).then(finish);
+ });
+ }
+
+ function finish()
+ {
+ toolbox = null;
+ finishTest();
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_split_escape_key.js b/browser/devtools/webconsole/test/browser_webconsole_split_escape_key.js
new file mode 100644
index 000000000..2b8b8e4e7
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_split_escape_key.js
@@ -0,0 +1,171 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function test() {
+ info("Test various cases where the escape key should hide the split console.");
+
+ let toolbox;
+ let hud;
+ let jsterm;
+ let hudMessages;
+ let variablesView;
+
+ Task.spawn(runner).then(finish);
+
+ function* runner() {
+ let {tab} = yield loadTab("data:text/html;charset=utf-8,<p>Web Console test for splitting");
+ let target = TargetFactory.forTab(tab);
+ toolbox = yield gDevTools.showToolbox(target, "inspector");
+
+ yield testCreateSplitConsoleAfterEscape();
+
+ yield showAutoCompletePopoup();
+
+ yield testHideAutoCompletePopupAfterEscape();
+
+ yield executeJS();
+ yield clickMessageAndShowVariablesView();
+ jsterm.inputNode.focus();
+
+ yield testHideVariablesViewAfterEscape();
+
+ yield clickMessageAndShowVariablesView();
+ yield startPropertyEditor();
+
+ yield testCancelPropertyEditorAfterEscape();
+ yield testHideVariablesViewAfterEscape();
+ yield testHideSplitConsoleAfterEscape();
+ }
+
+ function testCreateSplitConsoleAfterEscape() {
+ let result = toolbox.once("webconsole-ready", () => {
+ hud = toolbox.getPanel("webconsole").hud;
+ jsterm = hud.jsterm;
+ ok(toolbox.splitConsole, "Split console is created.");
+ });
+
+ let contentWindow = toolbox.frame.contentWindow;
+ contentWindow.focus();
+ EventUtils.sendKey("ESCAPE", contentWindow);
+
+ return result;
+ }
+
+ function testShowSplitConsoleAfterEscape() {
+ let result = toolbox.once("split-console", () => {
+ ok(toolbox.splitConsole, "Split console is shown.");
+ });
+ EventUtils.sendKey("ESCAPE", toolbox.frame.contentWindow);
+
+ return result;
+ }
+
+ function testHideSplitConsoleAfterEscape() {
+ let result = toolbox.once("split-console", () => {
+ ok(!toolbox.splitConsole, "Split console is hidden.");
+ });
+ EventUtils.sendKey("ESCAPE", toolbox.frame.contentWindow);
+
+ return result;
+ }
+
+ function testHideVariablesViewAfterEscape() {
+ let result = jsterm.once("sidebar-closed", () => {
+ ok(!hud.ui.jsterm.sidebar,
+ "Variables view is hidden.");
+ ok(toolbox.splitConsole,
+ "Split console is open after hiding the variables view.");
+ });
+ EventUtils.sendKey("ESCAPE", toolbox.frame.contentWindow);
+
+ return result;
+ }
+
+ function testHideAutoCompletePopupAfterEscape() {
+ let deferred = promise.defer();
+ let popup = jsterm.autocompletePopup;
+
+ popup._panel.addEventListener("popuphidden", function popupHidden() {
+ popup._panel.removeEventListener("popuphidden", popupHidden, false);
+ ok(!popup.isOpen,
+ "Auto complete popup is hidden.");
+ ok(toolbox.splitConsole,
+ "Split console is open after hiding the autocomplete popup.");
+
+ deferred.resolve();
+ }, false);
+
+ EventUtils.sendKey("ESCAPE", toolbox.frame.contentWindow);
+
+ return deferred.promise;
+ }
+
+ function testCancelPropertyEditorAfterEscape() {
+ EventUtils.sendKey("ESCAPE", variablesView.window);
+ ok(hud.ui.jsterm.sidebar,
+ "Variables view is open after canceling property editor.");
+ ok(toolbox.splitConsole,
+ "Split console is open after editing.");
+ }
+
+ function executeJS() {
+ jsterm.execute("var foo = { bar: \"baz\" }; foo;");
+ hudMessages = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "Object { bar: \"baz\" }",
+ category: CATEGORY_OUTPUT,
+ objects: true
+ }],
+ });
+ }
+
+ function clickMessageAndShowVariablesView() {
+ let result = jsterm.once("variablesview-fetched", (event, vview) => {
+ variablesView = vview;
+ });
+
+ let clickable = hudMessages[0].clickableElements[0];
+ EventUtils.synthesizeMouse(clickable, 2, 2, {}, hud.iframeWindow);
+
+ return result;
+ }
+
+ function startPropertyEditor() {
+ let results = yield findVariableViewProperties(variablesView, [
+ {name: "bar", value: "baz"}
+ ], {webconsole: hud});
+ results[0].matchedProp.focus();
+ EventUtils.synthesizeKey("VK_RETURN", variablesView.window);
+ }
+
+ function showAutoCompletePopoup() {
+ let deferred = promise.defer();
+ let popupPanel = jsterm.autocompletePopup._panel;
+
+ popupPanel.addEventListener("popupshown", function popupShown() {
+ popupPanel.removeEventListener("popupshown", popupShown, false);
+ deferred.resolve();
+ }, false);
+
+ jsterm.inputNode.focus();
+ jsterm.setInputValue("document.location.");
+ EventUtils.sendKey("TAB", hud.iframeWindow);
+
+ return deferred.promise;
+ }
+
+ function finish() {
+ toolbox.destroy().then(() => {
+ toolbox = null;
+ hud = null;
+ jsterm = null;
+ hudMessages = null;
+ variablesView = null;
+
+ finishTest();
+ });
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_split_focus.js b/browser/devtools/webconsole/test/browser_webconsole_split_focus.js
new file mode 100644
index 000000000..51d7b9a79
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_split_focus.js
@@ -0,0 +1,74 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function test() {
+ info("Test that the split console state is persisted");
+
+ let toolbox;
+ let TEST_URI = "data:text/html;charset=utf-8,<p>Web Console test for splitting</p>";
+
+ Task.spawn(runner).then(finish);
+
+ function* runner() {
+ info("Opening a tab while there is no user setting on split console pref");
+ let {tab} = yield loadTab(TEST_URI);
+ let target = TargetFactory.forTab(tab);
+ toolbox = yield gDevTools.showToolbox(target, "inspector");
+
+ ok(!toolbox.splitConsole, "Split console is hidden by default");
+
+ info ("Focusing the search box before opening the split console");
+ let inspector = toolbox.getPanel("inspector");
+ inspector.searchBox.focus();
+
+ // Use the binding element since inspector.searchBox is a XUL element.
+ let activeElement = getActiveElement(inspector.panelDoc);
+ activeElement = activeElement.ownerDocument.getBindingParent(activeElement);
+ is (activeElement, inspector.searchBox, "Search box is focused");
+
+ yield toolbox.openSplitConsole();
+
+ ok(toolbox.splitConsole, "Split console is now visible");
+
+ // Use the binding element since jsterm.inputNode is a XUL textarea element.
+ activeElement = getActiveElement(toolbox.doc);
+ activeElement = activeElement.ownerDocument.getBindingParent(activeElement);
+ let inputNode = toolbox.getPanel("webconsole").hud.jsterm.inputNode;
+ is(activeElement, inputNode, "Split console input is focused by default");
+
+ yield toolbox.closeSplitConsole();
+
+ info ("Making sure that the search box is refocused after closing the split console");
+ // Use the binding element since inspector.searchBox is a XUL element.
+ activeElement = getActiveElement(inspector.panelDoc);
+ activeElement = activeElement.ownerDocument.getBindingParent(activeElement);
+ is (activeElement, inspector.searchBox, "Search box is focused");
+
+ yield toolbox.destroy();
+ }
+
+ function getActiveElement(doc) {
+ let activeElement = doc.activeElement;
+ while (activeElement && activeElement.contentDocument) {
+ activeElement = activeElement.contentDocument.activeElement;
+ }
+ return activeElement;
+ }
+
+ function toggleSplitConsoleWithEscape() {
+ let onceSplitConsole = toolbox.once("split-console");
+ let contentWindow = toolbox.frame.contentWindow;
+ contentWindow.focus();
+ EventUtils.sendKey("ESCAPE", contentWindow);
+ return onceSplitConsole;
+ }
+
+ function finish() {
+ toolbox = TEST_URI = null;
+ Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
+ Services.prefs.clearUserPref("devtools.toolbox.splitconsoleHeight");
+ finishTest();
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_split_persist.js b/browser/devtools/webconsole/test/browser_webconsole_split_persist.js
new file mode 100644
index 000000000..d9116f2b1
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_split_persist.js
@@ -0,0 +1,111 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function test() {
+ info("Test that the split console state is persisted");
+
+ let toolbox;
+ let TEST_URI = "data:text/html;charset=utf-8,<p>Web Console test for splitting</p>";
+
+ Task.spawn(runner).then(finish);
+
+ function* runner() {
+ info("Opening a tab while there is no user setting on split console pref");
+ let {tab} = yield loadTab(TEST_URI);
+ let target = TargetFactory.forTab(tab);
+ toolbox = yield gDevTools.showToolbox(target, "inspector");
+
+ ok(!toolbox.splitConsole, "Split console is hidden by default.");
+ ok(!isCommandButtonChecked(), "Split console button is unchecked by default.");
+ yield toggleSplitConsoleWithEscape();
+ ok(toolbox.splitConsole, "Split console is now visible.");
+ ok(isCommandButtonChecked(), "Split console button is now checked.");
+ ok(getVisiblePrefValue(), "Visibility pref is true");
+
+ is(getHeightPrefValue(), toolbox.webconsolePanel.height, "Panel height matches the pref");
+ toolbox.webconsolePanel.height = 200;
+
+ yield toolbox.destroy();
+
+ info("Opening a tab while there is a true user setting on split console pref");
+ ({tab} = yield loadTab(TEST_URI));
+ target = TargetFactory.forTab(tab);
+ toolbox = yield gDevTools.showToolbox(target, "inspector");
+
+ ok(toolbox.splitConsole, "Split console is visible by default.");
+ ok(isCommandButtonChecked(), "Split console button is checked by default.");
+ is(getHeightPrefValue(), 200, "Height is set based on panel height after closing");
+
+ // Use the binding element since jsterm.inputNode is a XUL textarea element.
+ let activeElement = getActiveElement(toolbox.doc);
+ activeElement = activeElement.ownerDocument.getBindingParent(activeElement);
+ let inputNode = toolbox.getPanel("webconsole").hud.jsterm.inputNode;
+ is(activeElement, inputNode, "Split console input is focused by default");
+
+ toolbox.webconsolePanel.height = 1;
+ ok (toolbox.webconsolePanel.clientHeight > 1,
+ "The actual height of the console is bound with a min height");
+
+ toolbox.webconsolePanel.height = 10000;
+ ok (toolbox.webconsolePanel.clientHeight < 10000,
+ "The actual height of the console is bound with a max height");
+
+ yield toggleSplitConsoleWithEscape();
+ ok(!toolbox.splitConsole, "Split console is now hidden.");
+ ok(!isCommandButtonChecked(), "Split console button is now unchecked.");
+ ok(!getVisiblePrefValue(), "Visibility pref is false");
+
+ yield toolbox.destroy();
+
+ is(getHeightPrefValue(), 10000, "Height is set based on panel height after closing");
+
+
+ info("Opening a tab while there is a false user setting on split console pref");
+ ({tab} = yield loadTab(TEST_URI));
+ target = TargetFactory.forTab(tab);
+ toolbox = yield gDevTools.showToolbox(target, "inspector");
+
+ ok(!toolbox.splitConsole, "Split console is hidden by default.");
+ ok(!getVisiblePrefValue(), "Visibility pref is false");
+
+ yield toolbox.destroy();
+ }
+
+ function getActiveElement(doc) {
+ let activeElement = doc.activeElement;
+ while (activeElement && activeElement.contentDocument) {
+ activeElement = activeElement.contentDocument.activeElement;
+ }
+ return activeElement;
+ }
+
+ function getVisiblePrefValue() {
+ return Services.prefs.getBoolPref("devtools.toolbox.splitconsoleEnabled");
+ }
+
+ function getHeightPrefValue() {
+ return Services.prefs.getIntPref("devtools.toolbox.splitconsoleHeight");
+ }
+
+ function isCommandButtonChecked() {
+ return toolbox.doc.querySelector("#command-button-splitconsole").
+ hasAttribute("checked");
+ }
+
+ function toggleSplitConsoleWithEscape() {
+ let onceSplitConsole = toolbox.once("split-console");
+ let contentWindow = toolbox.frame.contentWindow;
+ contentWindow.focus();
+ EventUtils.sendKey("ESCAPE", contentWindow);
+ return onceSplitConsole;
+ }
+
+ function finish() {
+ toolbox = TEST_URI = null;
+ Services.prefs.clearUserPref("devtools.toolbox.splitconsoleEnabled");
+ Services.prefs.clearUserPref("devtools.toolbox.splitconsoleHeight");
+ finishTest();
+ }
+}
diff --git a/browser/devtools/webconsole/test/browser_webconsole_start_netmon_first.js b/browser/devtools/webconsole/test/browser_webconsole_start_netmon_first.js
new file mode 100644
index 000000000..b648da021
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_start_netmon_first.js
@@ -0,0 +1,37 @@
+/* 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/. */
+
+// Check that the webconsole works if the network monitor is first opened, then
+// the user switches to the webconsole. See bug 970914.
+
+function test() {
+ Task.spawn(runner).then(finishTest);
+
+ function* runner() {
+ const {tab} = yield loadTab("data:text/html;charset=utf8,<p>hello");
+
+ const target = TargetFactory.forTab(tab);
+ const toolbox = yield gDevTools.showToolbox(target, "netmonitor");
+
+ const hud = yield openConsole(tab);
+
+ hud.jsterm.execute("console.log('foobar bug970914')");
+
+ yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "console.log",
+ text: "foobar bug970914",
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ let text = hud.outputNode.textContent;
+ isnot(text.indexOf("foobar bug970914"), -1, "console.log message confirmed");
+ ok(!/logging API|disabled by a script/i.test(text),
+ "no warning about disabled console API");
+ }
+}
+
diff --git a/browser/devtools/webconsole/test/browser_webconsole_view_source.js b/browser/devtools/webconsole/test/browser_webconsole_view_source.js
new file mode 100644
index 000000000..ad008478d
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_view_source.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that source URLs in the Web Console can be clicked to display the
+// standard View Source window.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-error.html";
+
+let getItemForAttachment;
+let Sources;
+let getItemInvoked = false;
+
+function test() {
+ loadTab(TEST_URI).then(() => {
+ openConsole(null).then(testViewSource);
+ });
+}
+
+function testViewSource(hud) {
+ info("console opened");
+
+ let button = content.document.querySelector("button");
+ ok(button, "we have the button on the page");
+
+ expectUncaughtException();
+ EventUtils.sendMouseEvent({ type: "click" }, button, content);
+
+ openDebugger().then(({panelWin: { DebuggerView }}) => {
+ info("debugger opened");
+ Sources = DebuggerView.Sources;
+ openConsole().then((hud) => {
+ info("console opened again");
+
+ waitForMessages({
+ webconsole: hud,
+ messages: [{
+ text: "fooBazBaz is not defined",
+ category: CATEGORY_JS,
+ severity: SEVERITY_ERROR,
+ }],
+ }).then(onMessage);
+ });
+ });
+
+ function onMessage([result]) {
+ let msg = [...result.matched][0];
+ ok(msg, "error message");
+ let locationNode = msg.querySelector(".message-location");
+ ok(locationNode, "location node");
+
+ Services.ww.registerNotification(observer);
+
+ getItemForAttachment = Sources.getItemForAttachment;
+ Sources.getItemForAttachment = () => {
+ getItemInvoked = true;
+ return false;
+ };
+
+ EventUtils.sendMouseEvent({ type: "click" }, locationNode);
+ }
+}
+
+let observer = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic != "domwindowopened") {
+ return;
+ }
+
+ ok(true, "the view source window was opened in response to clicking " +
+ "the location node");
+
+ aSubject.close();
+ ok(getItemInvoked, "custom getItemForAttachment() was invoked");
+ Sources.getItemForAttachment = getItemForAttachment;
+ Sources = getItemForAttachment = null;
+ finishTest();
+ }
+};
+
+registerCleanupFunction(function() {
+ Services.ww.unregisterNotification(observer);
+});
diff --git a/browser/devtools/webconsole/test/head.js b/browser/devtools/webconsole/test/head.js
new file mode 100644
index 000000000..c49badb40
--- /dev/null
+++ b/browser/devtools/webconsole/test/head.js
@@ -0,0 +1,1677 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+"use strict";
+
+let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
+let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
+let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let {require, TargetFactory} = devtools;
+let {Utils: WebConsoleUtils} = require("devtools/toolkit/webconsole/utils");
+let {Messages} = require("devtools/webconsole/console-output");
+
+// promise._reportErrors = true; // please never leave me.
+//Services.prefs.setBoolPref("devtools.debugger.log", true);
+
+let gPendingOutputTest = 0;
+
+// The various categories of messages.
+const CATEGORY_NETWORK = 0;
+const CATEGORY_CSS = 1;
+const CATEGORY_JS = 2;
+const CATEGORY_WEBDEV = 3;
+const CATEGORY_INPUT = 4;
+const CATEGORY_OUTPUT = 5;
+const CATEGORY_SECURITY = 6;
+
+// The possible message severities.
+const SEVERITY_ERROR = 0;
+const SEVERITY_WARNING = 1;
+const SEVERITY_INFO = 2;
+const SEVERITY_LOG = 3;
+
+// The indent of a console group in pixels.
+const GROUP_INDENT = 12;
+
+const WEBCONSOLE_STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
+let WCU_l10n = new WebConsoleUtils.l10n(WEBCONSOLE_STRINGS_URI);
+
+gDevTools.testing = true;
+
+function asyncTest(generator) {
+ return () => {
+ Task.spawn(generator).then(finishTest);
+ };
+}
+
+
+function loadTab(url) {
+ let deferred = promise.defer();
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+ let browser = gBrowser.getBrowserForTab(tab);
+
+ browser.addEventListener("load", function onLoad() {
+ browser.removeEventListener("load", onLoad, true);
+ deferred.resolve({tab: tab, browser: browser});
+ }, true);
+
+ return deferred.promise;
+}
+
+function loadBrowser(browser) {
+ let deferred = promise.defer();
+
+ browser.addEventListener("load", function onLoad() {
+ browser.removeEventListener("load", onLoad, true);
+ deferred.resolve(null);
+ }, true);
+
+ return deferred.promise;
+}
+
+function closeTab(tab) {
+ let deferred = promise.defer();
+
+ let container = gBrowser.tabContainer;
+
+ container.addEventListener("TabClose", function onTabClose() {
+ container.removeEventListener("TabClose", onTabClose, true);
+ deferred.resolve(null);
+ }, true);
+
+ gBrowser.removeTab(tab);
+
+ return deferred.promise;
+}
+
+function afterAllTabsLoaded(callback, win) {
+ win = win || window;
+
+ let stillToLoad = 0;
+
+ function onLoad() {
+ this.removeEventListener("load", onLoad, true);
+ stillToLoad--;
+ if (!stillToLoad)
+ callback();
+ }
+
+ for (let a = 0; a < win.gBrowser.tabs.length; a++) {
+ let browser = win.gBrowser.tabs[a].linkedBrowser;
+ if (browser.webProgress.isLoadingDocument) {
+ stillToLoad++;
+ browser.addEventListener("load", onLoad, true);
+ }
+ }
+
+ if (!stillToLoad)
+ callback();
+}
+
+/**
+ * Check if a log entry exists in the HUD output node.
+ *
+ * @param {Element} aOutputNode
+ * the HUD output node.
+ * @param {string} aMatchString
+ * the string you want to check if it exists in the output node.
+ * @param {string} aMsg
+ * the message describing the test
+ * @param {boolean} [aOnlyVisible=false]
+ * find only messages that are visible, not hidden by the filter.
+ * @param {boolean} [aFailIfFound=false]
+ * fail the test if the string is found in the output node.
+ * @param {string} aClass [optional]
+ * find only messages with the given CSS class.
+ */
+function testLogEntry(aOutputNode, aMatchString, aMsg, aOnlyVisible,
+ aFailIfFound, aClass)
+{
+ let selector = ".message";
+ // Skip entries that are hidden by the filter.
+ if (aOnlyVisible) {
+ selector += ":not(.filtered-by-type):not(.filtered-by-string)";
+ }
+ if (aClass) {
+ selector += "." + aClass;
+ }
+
+ let msgs = aOutputNode.querySelectorAll(selector);
+ let found = false;
+ for (let i = 0, n = msgs.length; i < n; i++) {
+ let message = msgs[i].textContent.indexOf(aMatchString);
+ if (message > -1) {
+ found = true;
+ break;
+ }
+ }
+
+ is(found, !aFailIfFound, aMsg);
+}
+
+/**
+ * A convenience method to call testLogEntry().
+ *
+ * @param string aString
+ * The string to find.
+ */
+function findLogEntry(aString)
+{
+ testLogEntry(outputNode, aString, "found " + aString);
+}
+
+/**
+ * Open the Web Console for the given tab.
+ *
+ * @param nsIDOMElement [aTab]
+ * Optional tab element for which you want open the Web Console. The
+ * default tab is taken from the global variable |tab|.
+ * @param function [aCallback]
+ * Optional function to invoke after the Web Console completes
+ * initialization (web-console-created).
+ * @return object
+ * A promise that is resolved once the web console is open.
+ */
+let openConsole = function(aTab) {
+ let webconsoleOpened = promise.defer();
+ let target = TargetFactory.forTab(aTab || gBrowser.selectedTab);
+ gDevTools.showToolbox(target, "webconsole").then(toolbox => {
+ let hud = toolbox.getCurrentPanel().hud;
+ hud.jsterm._lazyVariablesView = false;
+ webconsoleOpened.resolve(hud);
+ });
+ return webconsoleOpened.promise;
+};
+
+/**
+ * Close the Web Console for the given tab.
+ *
+ * @param nsIDOMElement [aTab]
+ * Optional tab element for which you want close the Web Console. The
+ * default tab is taken from the global variable |tab|.
+ * @param function [aCallback]
+ * Optional function to invoke after the Web Console completes
+ * closing (web-console-destroyed).
+ * @return object
+ * A promise that is resolved once the web console is closed.
+ */
+let closeConsole = Task.async(function* (aTab) {
+ let target = TargetFactory.forTab(aTab || gBrowser.selectedTab);
+ let toolbox = gDevTools.getToolbox(target);
+ if (toolbox) {
+ yield toolbox.destroy();
+ }
+});
+
+/**
+ * Wait for a context menu popup to open.
+ *
+ * @param nsIDOMElement aPopup
+ * The XUL popup you expect to open.
+ * @param nsIDOMElement aButton
+ * The button/element that receives the contextmenu event. This is
+ * expected to open the popup.
+ * @param function aOnShown
+ * Function to invoke on popupshown event.
+ * @param function aOnHidden
+ * Function to invoke on popuphidden event.
+ * @return object
+ * A Promise object that is resolved after the popuphidden event
+ * callback is invoked.
+ */
+function waitForContextMenu(aPopup, aButton, aOnShown, aOnHidden)
+{
+ function onPopupShown() {
+ info("onPopupShown");
+ aPopup.removeEventListener("popupshown", onPopupShown);
+
+ aOnShown && aOnShown();
+
+ // Use executeSoon() to get out of the popupshown event.
+ aPopup.addEventListener("popuphidden", onPopupHidden);
+ executeSoon(() => aPopup.hidePopup());
+ }
+ function onPopupHidden() {
+ info("onPopupHidden");
+ aPopup.removeEventListener("popuphidden", onPopupHidden);
+
+ aOnHidden && aOnHidden();
+
+ deferred.resolve(aPopup);
+ }
+
+ let deferred = promise.defer();
+ aPopup.addEventListener("popupshown", onPopupShown);
+
+ info("wait for the context menu to open");
+ let eventDetails = { type: "contextmenu", button: 2};
+ EventUtils.synthesizeMouse(aButton, 2, 2, eventDetails,
+ aButton.ownerDocument.defaultView);
+ return deferred.promise;
+}
+
+/**
+ * Dump the output of all open Web Consoles - used only for debugging purposes.
+ */
+function dumpConsoles()
+{
+ if (gPendingOutputTest) {
+ console.log("dumpConsoles start");
+ for (let [, hud] of HUDService.consoles) {
+ if (!hud.outputNode) {
+ console.debug("no output content for", hud.hudId);
+ continue;
+ }
+
+ console.debug("output content for", hud.hudId);
+ for (let elem of hud.outputNode.childNodes) {
+ dumpMessageElement(elem);
+ }
+ }
+ console.log("dumpConsoles end");
+
+ gPendingOutputTest = 0;
+ }
+}
+
+/**
+ * Dump to output debug information for the given webconsole message.
+ *
+ * @param nsIDOMNode aMessage
+ * The message element you want to display.
+ */
+function dumpMessageElement(aMessage)
+{
+ let text = aMessage.textContent;
+ let repeats = aMessage.querySelector(".message-repeats");
+ if (repeats) {
+ repeats = repeats.getAttribute("value");
+ }
+ console.debug("id", aMessage.getAttribute("id"),
+ "date", aMessage.timestamp,
+ "class", aMessage.className,
+ "category", aMessage.category,
+ "severity", aMessage.severity,
+ "repeats", repeats,
+ "clipboardText", aMessage.clipboardText,
+ "text", text);
+}
+
+let finishTest = Task.async(function* () {
+ dumpConsoles();
+
+ let browserConsole = HUDService.getBrowserConsole();
+ if (browserConsole) {
+ if (browserConsole.jsterm) {
+ browserConsole.jsterm.clearOutput(true);
+ }
+ yield HUDService.toggleBrowserConsole();
+ }
+
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ yield gDevTools.closeToolbox(target);
+
+ finish();
+});
+
+registerCleanupFunction(function*() {
+ gDevTools.testing = false;
+
+ dumpConsoles();
+
+ if (HUDService.getBrowserConsole()) {
+ HUDService.toggleBrowserConsole();
+ }
+
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ yield gDevTools.closeToolbox(target);
+
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+waitForExplicitFinish();
+
+/**
+ * Polls a given function waiting for it to become true.
+ *
+ * @param object aOptions
+ * Options object with the following properties:
+ * - validator
+ * A validator function that returns a boolean. This is called every few
+ * milliseconds to check if the result is true. When it is true, the
+ * promise is resolved and polling stops. If validator never returns
+ * true, then polling timeouts after several tries and the promise is
+ * rejected.
+ * - name
+ * Name of test. This is used to generate the success and failure
+ * messages.
+ * - timeout
+ * Timeout for validator function, in milliseconds. Default is 5000.
+ * @return object
+ * A Promise object that is resolved based on the validator function.
+ */
+function waitForSuccess(aOptions)
+{
+ let deferred = promise.defer();
+ let start = Date.now();
+ let timeout = aOptions.timeout || 5000;
+ let {validator} = aOptions;
+
+
+ function wait()
+ {
+ if ((Date.now() - start) > timeout) {
+ // Log the failure.
+ ok(false, "Timed out while waiting for: " + aOptions.name);
+ deferred.reject(null);
+ return;
+ }
+
+ if (validator(aOptions)) {
+ ok(true, aOptions.name);
+ deferred.resolve(null);
+ }
+ else {
+ setTimeout(wait, 100);
+ }
+ }
+
+ setTimeout(wait, 100);
+
+ return deferred.promise;
+}
+
+let openInspector = Task.async(function* (aTab = gBrowser.selectedTab) {
+ let target = TargetFactory.forTab(aTab);
+ let toolbox = yield gDevTools.showToolbox(target, "inspector");
+ return toolbox.getCurrentPanel();
+});
+
+/**
+ * Find variables or properties in a VariablesView instance.
+ *
+ * @param object aView
+ * The VariablesView instance.
+ * @param array aRules
+ * The array of rules you want to match. Each rule is an object with:
+ * - name (string|regexp): property name to match.
+ * - value (string|regexp): property value to match.
+ * - isIterator (boolean): check if the property is an iterator.
+ * - isGetter (boolean): check if the property is a getter.
+ * - isGenerator (boolean): check if the property is a generator.
+ * - dontMatch (boolean): make sure the rule doesn't match any property.
+ * @param object aOptions
+ * Options for matching:
+ * - webconsole: the WebConsole instance we work with.
+ * @return object
+ * A promise object that is resolved when all the rules complete
+ * matching. The resolved callback is given an array of all the rules
+ * you wanted to check. Each rule has a new property: |matchedProp|
+ * which holds a reference to the Property object instance from the
+ * VariablesView. If the rule did not match, then |matchedProp| is
+ * undefined.
+ */
+function findVariableViewProperties(aView, aRules, aOptions)
+{
+ // Initialize the search.
+ function init()
+ {
+ // Separate out the rules that require expanding properties throughout the
+ // view.
+ let expandRules = [];
+ let rules = aRules.filter((aRule) => {
+ if (typeof aRule.name == "string" && aRule.name.indexOf(".") > -1) {
+ expandRules.push(aRule);
+ return false;
+ }
+ return true;
+ });
+
+ // Search through the view those rules that do not require any properties to
+ // be expanded. Build the array of matchers, outstanding promises to be
+ // resolved.
+ let outstanding = [];
+ finder(rules, aView, outstanding);
+
+ // Process the rules that need to expand properties.
+ let lastStep = processExpandRules.bind(null, expandRules);
+
+ // Return the results - a promise resolved to hold the updated aRules array.
+ let returnResults = onAllRulesMatched.bind(null, aRules);
+
+ return promise.all(outstanding).then(lastStep).then(returnResults);
+ }
+
+ function onMatch(aProp, aRule, aMatched)
+ {
+ if (aMatched && !aRule.matchedProp) {
+ aRule.matchedProp = aProp;
+ }
+ }
+
+ function finder(aRules, aVar, aPromises)
+ {
+ for (let [id, prop] of aVar) {
+ for (let rule of aRules) {
+ let matcher = matchVariablesViewProperty(prop, rule, aOptions);
+ aPromises.push(matcher.then(onMatch.bind(null, prop, rule)));
+ }
+ }
+ }
+
+ function processExpandRules(aRules)
+ {
+ let rule = aRules.shift();
+ if (!rule) {
+ return promise.resolve(null);
+ }
+
+ let deferred = promise.defer();
+ let expandOptions = {
+ rootVariable: aView,
+ expandTo: rule.name,
+ webconsole: aOptions.webconsole,
+ };
+
+ variablesViewExpandTo(expandOptions).then(function onSuccess(aProp) {
+ let name = rule.name;
+ let lastName = name.split(".").pop();
+ rule.name = lastName;
+
+ let matched = matchVariablesViewProperty(aProp, rule, aOptions);
+ return matched.then(onMatch.bind(null, aProp, rule)).then(function() {
+ rule.name = name;
+ });
+ }, function onFailure() {
+ return promise.resolve(null);
+ }).then(processExpandRules.bind(null, aRules)).then(function() {
+ deferred.resolve(null);
+ });
+
+ return deferred.promise;
+ }
+
+ function onAllRulesMatched(aRules)
+ {
+ for (let rule of aRules) {
+ let matched = rule.matchedProp;
+ if (matched && !rule.dontMatch) {
+ ok(true, "rule " + rule.name + " matched for property " + matched.name);
+ }
+ else if (matched && rule.dontMatch) {
+ ok(false, "rule " + rule.name + " should not match property " +
+ matched.name);
+ }
+ else {
+ ok(rule.dontMatch, "rule " + rule.name + " did not match any property");
+ }
+ }
+ return aRules;
+ }
+
+ return init();
+}
+
+/**
+ * Check if a given Property object from the variables view matches the given
+ * rule.
+ *
+ * @param object aProp
+ * The variable's view Property instance.
+ * @param object aRule
+ * Rules for matching the property. See findVariableViewProperties() for
+ * details.
+ * @param object aOptions
+ * Options for matching. See findVariableViewProperties().
+ * @return object
+ * A promise that is resolved when all the checks complete. Resolution
+ * result is a boolean that tells your promise callback the match
+ * result: true or false.
+ */
+function matchVariablesViewProperty(aProp, aRule, aOptions)
+{
+ function resolve(aResult) {
+ return promise.resolve(aResult);
+ }
+
+ if (aRule.name) {
+ let match = aRule.name instanceof RegExp ?
+ aRule.name.test(aProp.name) :
+ aProp.name == aRule.name;
+ if (!match) {
+ return resolve(false);
+ }
+ }
+
+ if (aRule.value) {
+ let displayValue = aProp.displayValue;
+ if (aProp.displayValueClassName == "token-string") {
+ displayValue = displayValue.substring(1, displayValue.length - 1);
+ }
+
+ let match = aRule.value instanceof RegExp ?
+ aRule.value.test(displayValue) :
+ displayValue == aRule.value;
+ if (!match) {
+ info("rule " + aRule.name + " did not match value, expected '" +
+ aRule.value + "', found '" + displayValue + "'");
+ return resolve(false);
+ }
+ }
+
+ if ("isGetter" in aRule) {
+ let isGetter = !!(aProp.getter && aProp.get("get"));
+ if (aRule.isGetter != isGetter) {
+ info("rule " + aRule.name + " getter test failed");
+ return resolve(false);
+ }
+ }
+
+ if ("isGenerator" in aRule) {
+ let isGenerator = aProp.displayValue == "Generator";
+ if (aRule.isGenerator != isGenerator) {
+ info("rule " + aRule.name + " generator test failed");
+ return resolve(false);
+ }
+ }
+
+ let outstanding = [];
+
+ if ("isIterator" in aRule) {
+ let isIterator = isVariableViewPropertyIterator(aProp, aOptions.webconsole);
+ outstanding.push(isIterator.then((aResult) => {
+ if (aResult != aRule.isIterator) {
+ info("rule " + aRule.name + " iterator test failed");
+ }
+ return aResult == aRule.isIterator;
+ }));
+ }
+
+ outstanding.push(promise.resolve(true));
+
+ return promise.all(outstanding).then(function _onMatchDone(aResults) {
+ let ruleMatched = aResults.indexOf(false) == -1;
+ return resolve(ruleMatched);
+ });
+}
+
+/**
+ * Check if the given variables view property is an iterator.
+ *
+ * @param object aProp
+ * The Property instance you want to check.
+ * @param object aWebConsole
+ * The WebConsole instance to work with.
+ * @return object
+ * A promise that is resolved when the check completes. The resolved
+ * callback is given a boolean: true if the property is an iterator, or
+ * false otherwise.
+ */
+function isVariableViewPropertyIterator(aProp, aWebConsole)
+{
+ if (aProp.displayValue == "Iterator") {
+ return promise.resolve(true);
+ }
+
+ let deferred = promise.defer();
+
+ variablesViewExpandTo({
+ rootVariable: aProp,
+ expandTo: "__proto__.__iterator__",
+ webconsole: aWebConsole,
+ }).then(function onSuccess(aProp) {
+ deferred.resolve(true);
+ }, function onFailure() {
+ deferred.resolve(false);
+ });
+
+ return deferred.promise;
+}
+
+
+/**
+ * Recursively expand the variables view up to a given property.
+ *
+ * @param aOptions
+ * Options for view expansion:
+ * - rootVariable: start from the given scope/variable/property.
+ * - expandTo: string made up of property names you want to expand.
+ * For example: "body.firstChild.nextSibling" given |rootVariable:
+ * document|.
+ * - webconsole: a WebConsole instance. If this is not provided all
+ * property expand() calls will be considered sync. Things may fail!
+ * @return object
+ * A promise that is resolved only when the last property in |expandTo|
+ * is found, and rejected otherwise. Resolution reason is always the
+ * last property - |nextSibling| in the example above. Rejection is
+ * always the last property that was found.
+ */
+function variablesViewExpandTo(aOptions)
+{
+ let root = aOptions.rootVariable;
+ let expandTo = aOptions.expandTo.split(".");
+ let jsterm = (aOptions.webconsole || {}).jsterm;
+ let lastDeferred = promise.defer();
+
+ function fetch(aProp)
+ {
+ if (!aProp.onexpand) {
+ ok(false, "property " + aProp.name + " cannot be expanded: !onexpand");
+ return promise.reject(aProp);
+ }
+
+ let deferred = promise.defer();
+
+ if (aProp._fetched || !jsterm) {
+ executeSoon(function() {
+ deferred.resolve(aProp);
+ });
+ }
+ else {
+ jsterm.once("variablesview-fetched", function _onFetchProp() {
+ executeSoon(() => deferred.resolve(aProp));
+ });
+ }
+
+ aProp.expand();
+
+ return deferred.promise;
+ }
+
+ function getNext(aProp)
+ {
+ let name = expandTo.shift();
+ let newProp = aProp.get(name);
+
+ if (expandTo.length > 0) {
+ ok(newProp, "found property " + name);
+ if (newProp) {
+ fetch(newProp).then(getNext, fetchError);
+ }
+ else {
+ lastDeferred.reject(aProp);
+ }
+ }
+ else {
+ if (newProp) {
+ lastDeferred.resolve(newProp);
+ }
+ else {
+ lastDeferred.reject(aProp);
+ }
+ }
+ }
+
+ function fetchError(aProp)
+ {
+ lastDeferred.reject(aProp);
+ }
+
+ if (!root._fetched) {
+ fetch(root).then(getNext, fetchError);
+ }
+ else {
+ getNext(root);
+ }
+
+ return lastDeferred.promise;
+}
+
+
+/**
+ * Update the content of a property in the variables view.
+ *
+ * @param object aOptions
+ * Options for the property update:
+ * - property: the property you want to change.
+ * - field: string that tells what you want to change:
+ * - use "name" to change the property name,
+ * - or "value" to change the property value.
+ * - string: the new string to write into the field.
+ * - webconsole: reference to the Web Console instance we work with.
+ * @return object
+ * A Promise object that is resolved once the property is updated.
+ */
+let updateVariablesViewProperty = Task.async(function* (aOptions) {
+ let view = aOptions.property._variablesView;
+ view.window.focus();
+ aOptions.property.focus();
+
+ switch (aOptions.field) {
+ case "name":
+ EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true }, view.window);
+ break;
+ case "value":
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.window);
+ break;
+ default:
+ throw new Error("options.field is incorrect");
+ }
+
+ let deferred = promise.defer();
+
+ executeSoon(() => {
+ EventUtils.synthesizeKey("A", { accelKey: true }, view.window);
+
+ for (let c of aOptions.string) {
+ EventUtils.synthesizeKey(c, {}, view.window);
+ }
+
+ if (aOptions.webconsole) {
+ aOptions.webconsole.jsterm.once("variablesview-fetched").then((varView) => {
+ deferred.resolve(varView);
+ });
+ }
+
+ EventUtils.synthesizeKey("VK_RETURN", {}, view.window);
+
+ if (!aOptions.webconsole) {
+ executeSoon(() => {
+ deferred.resolve(null);
+ });
+ }
+ });
+
+ return deferred.promise;
+});
+
+/**
+ * Open the JavaScript debugger.
+ *
+ * @param object aOptions
+ * Options for opening the debugger:
+ * - tab: the tab you want to open the debugger for.
+ * @return object
+ * A promise that is resolved once the debugger opens, or rejected if
+ * the open fails. The resolution callback is given one argument, an
+ * object that holds the following properties:
+ * - target: the Target object for the Tab.
+ * - toolbox: the Toolbox instance.
+ * - panel: the jsdebugger panel instance.
+ * - panelWin: the window object of the panel iframe.
+ */
+function openDebugger(aOptions = {})
+{
+ if (!aOptions.tab) {
+ aOptions.tab = gBrowser.selectedTab;
+ }
+
+ let deferred = promise.defer();
+
+ let target = TargetFactory.forTab(aOptions.tab);
+ let toolbox = gDevTools.getToolbox(target);
+ let dbgPanelAlreadyOpen = toolbox.getPanel("jsdebugger");
+
+ gDevTools.showToolbox(target, "jsdebugger").then(function onSuccess(aToolbox) {
+ let panel = aToolbox.getCurrentPanel();
+ let panelWin = panel.panelWin;
+
+ panel._view.Variables.lazyEmpty = false;
+
+ let resolveObject = {
+ target: target,
+ toolbox: aToolbox,
+ panel: panel,
+ panelWin: panelWin,
+ };
+
+ if (dbgPanelAlreadyOpen) {
+ deferred.resolve(resolveObject);
+ }
+ else {
+ panelWin.once(panelWin.EVENTS.SOURCES_ADDED, () => {
+ deferred.resolve(resolveObject);
+ });
+ }
+ }, function onFailure(aReason) {
+ console.debug("failed to open the toolbox for 'jsdebugger'", aReason);
+ deferred.reject(aReason);
+ });
+
+ return deferred.promise;
+}
+
+/**
+ * Wait for messages in the Web Console output.
+ *
+ * @param object aOptions
+ * Options for what you want to wait for:
+ * - webconsole: the webconsole instance you work with.
+ * - matchCondition: "any" or "all". Default: "all". The promise
+ * returned by this function resolves when all of the messages are
+ * matched, if the |matchCondition| is "all". If you set the condition to
+ * "any" then the promise is resolved by any message rule that matches,
+ * irrespective of order - waiting for messages stops whenever any rule
+ * matches.
+ * - messages: an array of objects that tells which messages to wait for.
+ * Properties:
+ * - text: string or RegExp to match the textContent of each new
+ * message.
+ * - noText: string or RegExp that must not match in the message
+ * textContent.
+ * - repeats: the number of message repeats, as displayed by the Web
+ * Console.
+ * - category: match message category. See CATEGORY_* constants at
+ * the top of this file.
+ * - severity: match message severity. See SEVERITY_* constants at
+ * the top of this file.
+ * - count: how many unique web console messages should be matched by
+ * this rule.
+ * - consoleTrace: boolean, set to |true| to match a console.trace()
+ * message. Optionally this can be an object of the form
+ * { file, fn, line } that can match the specified file, function
+ * and/or line number in the trace message.
+ * - consoleTime: string that matches a console.time() timer name.
+ * Provide this if you want to match a console.time() message.
+ * - consoleTimeEnd: same as above, but for console.timeEnd().
+ * - consoleDir: boolean, set to |true| to match a console.dir()
+ * message.
+ * - consoleGroup: boolean, set to |true| to match a console.group()
+ * message.
+ * - consoleTable: boolean, set to |true| to match a console.table()
+ * message.
+ * - longString: boolean, set to |true} to match long strings in the
+ * message.
+ * - collapsible: boolean, set to |true| to match messages that can
+ * be collapsed/expanded.
+ * - type: match messages that are instances of the given object. For
+ * example, you can point to Messages.NavigationMarker to match any
+ * such message.
+ * - objects: boolean, set to |true| if you expect inspectable
+ * objects in the message.
+ * - source: object of the shape { url, line }. This is used to
+ * match the source URL and line number of the error message or
+ * console API call.
+ * - stacktrace: array of objects of the form { file, fn, line } that
+ * can match frames in the stacktrace associated with the message.
+ * - groupDepth: number used to check the depth of the message in
+ * a group.
+ * - url: URL to match for network requests.
+ * @return object
+ * A promise object is returned once the messages you want are found.
+ * The promise is resolved with the array of rule objects you give in
+ * the |messages| property. Each objects is the same as provided, with
+ * additional properties:
+ * - matched: a Set of web console messages that matched the rule.
+ * - clickableElements: a list of inspectable objects. This is available
+ * if any of the following properties are present in the rule:
+ * |consoleTrace| or |objects|.
+ * - longStrings: a list of long string ellipsis elements you can click
+ * in the message element, to expand a long string. This is available
+ * only if |longString| is present in the matching rule.
+ */
+function waitForMessages(aOptions)
+{
+ info("Waiting for messages...");
+
+ gPendingOutputTest++;
+ let webconsole = aOptions.webconsole;
+ let rules = WebConsoleUtils.cloneObject(aOptions.messages, true);
+ let rulesMatched = 0;
+ let listenerAdded = false;
+ let deferred = promise.defer();
+ aOptions.matchCondition = aOptions.matchCondition || "all";
+
+ function checkText(aRule, aText)
+ {
+ let result = false;
+ if (Array.isArray(aRule)) {
+ result = aRule.every((s) => checkText(s, aText));
+ }
+ else if (typeof aRule == "string") {
+ result = aText.indexOf(aRule) > -1;
+ }
+ else if (aRule instanceof RegExp) {
+ result = aRule.test(aText);
+ }
+ else {
+ result = aRule == aText;
+ }
+ return result;
+ }
+
+ function checkConsoleTable(aRule, aElement)
+ {
+ let elemText = aElement.textContent;
+ let table = aRule.consoleTable;
+
+ if (!checkText("console.table():", elemText)) {
+ return false;
+ }
+
+ aRule.category = CATEGORY_WEBDEV;
+ aRule.severity = SEVERITY_LOG;
+ aRule.type = Messages.ConsoleTable;
+
+ return true;
+ }
+
+ function checkConsoleTrace(aRule, aElement)
+ {
+ let elemText = aElement.textContent;
+ let trace = aRule.consoleTrace;
+
+ if (!checkText("console.trace():", elemText)) {
+ return false;
+ }
+
+ aRule.category = CATEGORY_WEBDEV;
+ aRule.severity = SEVERITY_LOG;
+ aRule.type = Messages.ConsoleTrace;
+
+ if (!aRule.stacktrace && typeof trace == "object" && trace !== true) {
+ if (Array.isArray(trace)) {
+ aRule.stacktrace = trace;
+ } else {
+ aRule.stacktrace = [trace];
+ }
+ }
+
+ return true;
+ }
+
+ function checkConsoleTime(aRule, aElement)
+ {
+ let elemText = aElement.textContent;
+ let time = aRule.consoleTime;
+
+ if (!checkText(time + ": timer started", elemText)) {
+ return false;
+ }
+
+ aRule.category = CATEGORY_WEBDEV;
+ aRule.severity = SEVERITY_LOG;
+
+ return true;
+ }
+
+ function checkConsoleTimeEnd(aRule, aElement)
+ {
+ let elemText = aElement.textContent;
+ let time = aRule.consoleTimeEnd;
+ let regex = new RegExp(time + ": -?\\d+([,.]\\d+)?ms");
+
+ if (!checkText(regex, elemText)) {
+ return false;
+ }
+
+ aRule.category = CATEGORY_WEBDEV;
+ aRule.severity = SEVERITY_LOG;
+
+ return true;
+ }
+
+ function checkConsoleDir(aRule, aElement)
+ {
+ if (!aElement.classList.contains("inlined-variables-view")) {
+ return false;
+ }
+
+ let elemText = aElement.textContent;
+ if (!checkText(aRule.consoleDir, elemText)) {
+ return false;
+ }
+
+ let iframe = aElement.querySelector("iframe");
+ if (!iframe) {
+ ok(false, "console.dir message has no iframe");
+ return false;
+ }
+
+ return true;
+ }
+
+ function checkConsoleGroup(aRule, aElement)
+ {
+ if (!isNaN(parseInt(aRule.consoleGroup))) {
+ aRule.groupDepth = aRule.consoleGroup;
+ }
+ aRule.category = CATEGORY_WEBDEV;
+ aRule.severity = SEVERITY_LOG;
+
+ return true;
+ }
+
+ function checkSource(aRule, aElement)
+ {
+ let location = aElement.querySelector(".message-location");
+ if (!location) {
+ return false;
+ }
+
+ if (!checkText(aRule.source.url, location.getAttribute("title"))) {
+ return false;
+ }
+
+ if ("line" in aRule.source && location.sourceLine != aRule.source.line) {
+ return false;
+ }
+
+ return true;
+ }
+
+ function checkCollapsible(aRule, aElement)
+ {
+ let msg = aElement._messageObject;
+ if (!msg || !!msg.collapsible != aRule.collapsible) {
+ return false;
+ }
+
+ return true;
+ }
+
+ function checkStacktrace(aRule, aElement)
+ {
+ let stack = aRule.stacktrace;
+ let frames = aElement.querySelectorAll(".stacktrace > li");
+ if (!frames.length) {
+ return false;
+ }
+
+ for (let i = 0; i < stack.length; i++) {
+ let frame = frames[i];
+ let expected = stack[i];
+ if (!frame) {
+ ok(false, "expected frame #" + i + " but didnt find it");
+ return false;
+ }
+
+ if (expected.file) {
+ let file = frame.querySelector(".message-location").title;
+ if (!checkText(expected.file, file)) {
+ ok(false, "frame #" + i + " does not match file name: " +
+ expected.file);
+ displayErrorContext(aRule, aElement);
+ return false;
+ }
+ }
+
+ if (expected.fn) {
+ let fn = frame.querySelector(".function").textContent;
+ if (!checkText(expected.fn, fn)) {
+ ok(false, "frame #" + i + " does not match the function name: " +
+ expected.fn);
+ displayErrorContext(aRule, aElement);
+ return false;
+ }
+ }
+
+ if (expected.line) {
+ let line = frame.querySelector(".message-location").sourceLine;
+ if (!checkText(expected.line, line)) {
+ ok(false, "frame #" + i + " does not match the line number: " +
+ expected.line);
+ displayErrorContext(aRule, aElement);
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ function hasXhrLabel(aElement) {
+ let xhr = aElement.querySelector('.xhr');
+ if (!xhr) {
+ return false;
+ }
+ return true;
+ }
+
+ function checkMessage(aRule, aElement)
+ {
+ let elemText = aElement.textContent;
+
+ if (aRule.text && !checkText(aRule.text, elemText)) {
+ return false;
+ }
+
+ if (aRule.noText && checkText(aRule.noText, elemText)) {
+ return false;
+ }
+
+ if (aRule.consoleTable && !checkConsoleTable(aRule, aElement)) {
+ return false;
+ }
+
+ if (aRule.consoleTrace && !checkConsoleTrace(aRule, aElement)) {
+ return false;
+ }
+
+ if (aRule.consoleTime && !checkConsoleTime(aRule, aElement)) {
+ return false;
+ }
+
+ if (aRule.consoleTimeEnd && !checkConsoleTimeEnd(aRule, aElement)) {
+ return false;
+ }
+
+ if (aRule.consoleDir && !checkConsoleDir(aRule, aElement)) {
+ return false;
+ }
+
+ if (aRule.consoleGroup && !checkConsoleGroup(aRule, aElement)) {
+ return false;
+ }
+
+ if (aRule.source && !checkSource(aRule, aElement)) {
+ return false;
+ }
+
+ if ("collapsible" in aRule && !checkCollapsible(aRule, aElement)) {
+ return false;
+ }
+
+ if (aRule.isXhr && !hasXhrLabel(aElement)) {
+ return false;
+ }
+
+ if (!aRule.isXhr && hasXhrLabel(aElement)) {
+ return false;
+ }
+
+ let partialMatch = !!(aRule.consoleTrace || aRule.consoleTime ||
+ aRule.consoleTimeEnd);
+
+ // The rule tries to match the newer types of messages, based on their
+ // object constructor.
+ if (aRule.type) {
+ if (!aElement._messageObject ||
+ !(aElement._messageObject instanceof aRule.type)) {
+ if (partialMatch) {
+ ok(false, "message type for rule: " + displayRule(aRule));
+ displayErrorContext(aRule, aElement);
+ }
+ return false;
+ }
+ partialMatch = true;
+ }
+
+ if ("category" in aRule && aElement.category != aRule.category) {
+ if (partialMatch) {
+ is(aElement.category, aRule.category,
+ "message category for rule: " + displayRule(aRule));
+ displayErrorContext(aRule, aElement);
+ }
+ return false;
+ }
+
+ if ("severity" in aRule && aElement.severity != aRule.severity) {
+ if (partialMatch) {
+ is(aElement.severity, aRule.severity,
+ "message severity for rule: " + displayRule(aRule));
+ displayErrorContext(aRule, aElement);
+ }
+ return false;
+ }
+
+ if (aRule.text) {
+ partialMatch = true;
+ }
+
+ if (aRule.stacktrace && !checkStacktrace(aRule, aElement)) {
+ if (partialMatch) {
+ ok(false, "failed to match stacktrace for rule: " + displayRule(aRule));
+ displayErrorContext(aRule, aElement);
+ }
+ return false;
+ }
+
+ if (aRule.category == CATEGORY_NETWORK && "url" in aRule &&
+ !checkText(aRule.url, aElement.url)) {
+ return false;
+ }
+
+ if ("repeats" in aRule) {
+ let repeats = aElement.querySelector(".message-repeats");
+ if (!repeats || repeats.getAttribute("value") != aRule.repeats) {
+ return false;
+ }
+ }
+
+ if ("groupDepth" in aRule) {
+ let indentNode = aElement.querySelector(".indent");
+ let indent = (GROUP_INDENT * aRule.groupDepth) + "px";
+ if (!indentNode || indentNode.style.width != indent) {
+ is(indentNode.style.width, indent,
+ "group depth check failed for message rule: " + displayRule(aRule));
+ return false;
+ }
+ }
+
+ if ("longString" in aRule) {
+ let longStrings = aElement.querySelectorAll(".longStringEllipsis");
+ if (aRule.longString != !!longStrings[0]) {
+ if (partialMatch) {
+ is(!!longStrings[0], aRule.longString,
+ "long string existence check failed for message rule: " +
+ displayRule(aRule));
+ displayErrorContext(aRule, aElement);
+ }
+ return false;
+ }
+ aRule.longStrings = longStrings;
+ }
+
+ if ("objects" in aRule) {
+ let clickables = aElement.querySelectorAll(".message-body a");
+ if (aRule.objects != !!clickables[0]) {
+ if (partialMatch) {
+ is(!!clickables[0], aRule.objects,
+ "objects existence check failed for message rule: " +
+ displayRule(aRule));
+ displayErrorContext(aRule, aElement);
+ }
+ return false;
+ }
+ aRule.clickableElements = clickables;
+ }
+
+ let count = aRule.count || 1;
+ if (!aRule.matched) {
+ aRule.matched = new Set();
+ }
+ aRule.matched.add(aElement);
+
+ return aRule.matched.size == count;
+ }
+
+ function onMessagesAdded(aEvent, aNewMessages)
+ {
+ for (let msg of aNewMessages) {
+ let elem = msg.node;
+ let location = elem.querySelector(".message-location");
+ if (location) {
+ let url = location.title;
+ // Prevent recursion with the browser console and any potential
+ // messages coming from head.js.
+ if (url.indexOf("browser/devtools/webconsole/test/head.js") != -1) {
+ continue;
+ }
+ }
+
+ for (let rule of rules) {
+ if (rule._ruleMatched) {
+ continue;
+ }
+
+ let matched = checkMessage(rule, elem);
+ if (matched) {
+ rule._ruleMatched = true;
+ rulesMatched++;
+ ok(1, "matched rule: " + displayRule(rule));
+ if (maybeDone()) {
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ function allRulesMatched()
+ {
+ return aOptions.matchCondition == "all" && rulesMatched == rules.length ||
+ aOptions.matchCondition == "any" && rulesMatched > 0;
+ }
+
+ function maybeDone()
+ {
+ if (allRulesMatched()) {
+ if (listenerAdded) {
+ webconsole.ui.off("new-messages", onMessagesAdded);
+ }
+ gPendingOutputTest--;
+ deferred.resolve(rules);
+ return true;
+ }
+ return false;
+ }
+
+ function testCleanup() {
+ if (allRulesMatched()) {
+ return;
+ }
+
+ if (webconsole.ui) {
+ webconsole.ui.off("new-messages", onMessagesAdded);
+ }
+
+ for (let rule of rules) {
+ if (!rule._ruleMatched) {
+ ok(false, "failed to match rule: " + displayRule(rule));
+ }
+ }
+ }
+
+ function displayRule(aRule)
+ {
+ return aRule.name || aRule.text;
+ }
+
+ function displayErrorContext(aRule, aElement)
+ {
+ console.log("error occured during rule " + displayRule(aRule));
+ console.log("while checking the following message");
+ dumpMessageElement(aElement);
+ }
+
+ executeSoon(() => {
+
+ let messages = [];
+ for (let elem of webconsole.outputNode.childNodes) {
+ messages.push({
+ node: elem,
+ update: false,
+ });
+ }
+
+ onMessagesAdded("new-messages", messages);
+
+ if (!allRulesMatched()) {
+ listenerAdded = true;
+ registerCleanupFunction(testCleanup);
+ webconsole.ui.on("new-messages", onMessagesAdded);
+ }
+ });
+
+ return deferred.promise;
+}
+
+function whenDelayedStartupFinished(aWindow, aCallback)
+{
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ executeSoon(aCallback);
+ }
+ }, "browser-delayed-startup-finished", false);
+}
+
+/**
+ * Check the web console output for the given inputs. Each input is checked for
+ * the expected JS eval result, the result of calling print(), the result of
+ * console.log(). The JS eval result is also checked if it opens the variables
+ * view on click.
+ *
+ * @param object hud
+ * The web console instance to work with.
+ * @param array inputTests
+ * An array of input tests. An input test element is an object. Each
+ * object has the following properties:
+ * - input: string, JS input value to execute.
+ *
+ * - output: string|RegExp, expected JS eval result.
+ *
+ * - inspectable: boolean, when true, the test runner expects the JS eval
+ * result is an object that can be clicked for inspection.
+ *
+ * - noClick: boolean, when true, the test runner does not click the JS
+ * eval result. Some objects, like |window|, have a lot of properties and
+ * opening vview for them is very slow (they can cause timeouts in debug
+ * builds).
+ *
+ * - printOutput: string|RegExp, optional, expected output for
+ * |print(input)|. If this is not provided, printOutput = output.
+ *
+ * - variablesViewLabel: string|RegExp, optional, the expected variables
+ * view label when the object is inspected. If this is not provided, then
+ * |output| is used.
+ *
+ * - inspectorIcon: boolean, when true, the test runner expects the
+ * result widget to contain an inspectorIcon element (className
+ * open-inspector).
+ *
+ * - expectedTab: string, optional, the full URL of the new tab which must
+ * open. If this is not provided, any new tabs that open will cause a test
+ * failure.
+ */
+function checkOutputForInputs(hud, inputTests)
+{
+ let container = gBrowser.tabContainer;
+
+ function* runner()
+ {
+ for (let [i, entry] of inputTests.entries()) {
+ info("checkInput(" + i + "): " + entry.input);
+ yield checkInput(entry);
+ }
+ container = null;
+ }
+
+ function* checkInput(entry)
+ {
+ yield checkConsoleLog(entry);
+ yield checkPrintOutput(entry);
+ yield checkJSEval(entry);
+ }
+
+ function* checkConsoleLog(entry)
+ {
+ info("Logging: " + entry.input);
+ hud.jsterm.clearOutput();
+ hud.jsterm.execute("console.log(" + entry.input + ")");
+
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "console.log() output: " + entry.output,
+ text: entry.output,
+ category: CATEGORY_WEBDEV,
+ severity: SEVERITY_LOG,
+ }],
+ });
+
+ if (typeof entry.inspectorIcon == "boolean") {
+ let msg = [...result.matched][0];
+ yield checkLinkToInspector(entry, msg);
+ }
+ }
+
+ function checkPrintOutput(entry)
+ {
+ info("Printing: " + entry.input);
+ hud.jsterm.clearOutput();
+ hud.jsterm.execute("print(" + entry.input + ")");
+
+ let printOutput = entry.printOutput || entry.output;
+
+ return waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "print() output: " + printOutput,
+ text: printOutput,
+ category: CATEGORY_OUTPUT,
+ }],
+ });
+ }
+
+ function* checkJSEval(entry)
+ {
+ info("Evaluating: " + entry.input);
+ hud.jsterm.clearOutput();
+ hud.jsterm.execute(entry.input);
+
+ let [result] = yield waitForMessages({
+ webconsole: hud,
+ messages: [{
+ name: "JS eval output: " + entry.output,
+ text: entry.output,
+ category: CATEGORY_OUTPUT,
+ }],
+ });
+
+ let msg = [...result.matched][0];
+ if (!entry.noClick) {
+ yield checkObjectClick(entry, msg);
+ }
+ if (typeof entry.inspectorIcon == "boolean") {
+ yield checkLinkToInspector(entry, msg);
+ }
+ }
+
+ function* checkObjectClick(entry, msg)
+ {
+ info("Clicking: " + entry.input);
+ let body = msg.querySelector(".message-body a") ||
+ msg.querySelector(".message-body");
+ ok(body, "the message body");
+
+ let deferredVariablesView = promise.defer();
+ entry._onVariablesViewOpen = onVariablesViewOpen.bind(null, entry, deferredVariablesView);
+ hud.jsterm.on("variablesview-open", entry._onVariablesViewOpen);
+
+ let deferredTab = promise.defer();
+ entry._onTabOpen = onTabOpen.bind(null, entry, deferredTab);
+ container.addEventListener("TabOpen", entry._onTabOpen, true);
+
+ body.scrollIntoView();
+ EventUtils.synthesizeMouse(body, 2, 2, {}, hud.iframeWindow);
+
+ if (entry.inspectable) {
+ info("message body tagName '" + body.tagName + "' className '" + body.className + "'");
+ yield deferredVariablesView.promise;
+ } else {
+ hud.jsterm.off("variablesview-open", entry._onVariablesView);
+ entry._onVariablesView = null;
+ }
+
+ if (entry.expectedTab) {
+ yield deferredTab.promise;
+ } else {
+ container.removeEventListener("TabOpen", entry._onTabOpen, true);
+ entry._onTabOpen = null;
+ }
+
+ yield promise.resolve(null);
+ }
+
+ function checkLinkToInspector(entry, msg)
+ {
+ info("Checking Inspector Link: " + entry.input);
+ let elementNodeWidget = [...msg._messageObject.widgets][0];
+ if (!elementNodeWidget) {
+ ok(!entry.inspectorIcon, "The message has no ElementNode widget");
+ return;
+ }
+
+ return elementNodeWidget.linkToInspector().then(() => {
+ // linkToInspector resolved, check for the .open-inspector element
+ if (entry.inspectorIcon) {
+ ok(msg.querySelectorAll(".open-inspector").length,
+ "The ElementNode widget is linked to the inspector");
+ } else {
+ ok(!msg.querySelectorAll(".open-inspector").length,
+ "The ElementNode widget isn't linked to the inspector");
+ }
+ }, () => {
+ // linkToInspector promise rejected, node not linked to inspector
+ ok(!entry.inspectorIcon, "The ElementNode widget isn't linked to the inspector");
+ });
+ }
+
+ function onVariablesViewOpen(entry, {resolve, reject}, event, view, options)
+ {
+ info("Variables view opened: " + entry.input);
+ let label = entry.variablesViewLabel || entry.output;
+ if (typeof label == "string" && options.label != label) {
+ return;
+ }
+ if (label instanceof RegExp && !label.test(options.label)) {
+ return;
+ }
+
+ hud.jsterm.off("variablesview-open", entry._onVariablesViewOpen);
+ entry._onVariablesViewOpen = null;
+ ok(entry.inspectable, "variables view was shown");
+
+ resolve(null);
+ }
+
+ function onTabOpen(entry, {resolve, reject}, event)
+ {
+ container.removeEventListener("TabOpen", entry._onTabOpen, true);
+ entry._onTabOpen = null;
+
+ let tab = event.target;
+ let browser = gBrowser.getBrowserForTab(tab);
+ loadBrowser(browser).then(() => {
+ let uri = content.location.href;
+ ok(entry.expectedTab && entry.expectedTab == uri,
+ "opened tab '" + uri + "', expected tab '" + entry.expectedTab + "'");
+ return closeTab(tab);
+ }).then(resolve, reject);
+ }
+
+ return Task.spawn(runner);
+}
+
+/**
+ * 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 + ".");
+
+ let deferred = promise.defer();
+
+ for (let [add, remove] of [
+ ["addEventListener", "removeEventListener"],
+ ["addListener", "removeListener"],
+ ["on", "off"]
+ ]) {
+ if ((add in target) && (remove in target)) {
+ target[add](eventName, function onEvent(...aArgs) {
+ target[remove](eventName, onEvent, useCapture);
+ deferred.resolve.apply(deferred, aArgs);
+ }, useCapture);
+ break;
+ }
+ }
+
+ return deferred.promise;
+}
+
+function getSourceActor(aSources, aURL) {
+ let item = aSources.getItemForAttachment(a => a.source.url === aURL);
+ return item && item.value;
+}
+
+/**
+ * Verify that clicking on a link from a popup notification message tries to
+ * open the expected URL.
+ */
+function simulateMessageLinkClick(element, expectedLink) {
+ let deferred = promise.defer();
+
+ // Invoke the click event and check if a new tab would
+ // open to the correct page.
+ let oldOpenUILinkIn = window.openUILinkIn;
+ window.openUILinkIn = function(link) {
+ if (link == expectedLink) {
+ ok(true, "Clicking the message link opens the desired page");
+ window.openUILinkIn = oldOpenUILinkIn;
+ deferred.resolve();
+ }
+ };
+
+ let event = new MouseEvent("click", {
+ detail: 1,
+ button: 0,
+ bubbles: true,
+ cancelable: true
+ });
+ element.dispatchEvent(event);
+
+ return deferred.promise;
+}
diff --git a/browser/devtools/webconsole/test/test-autocomplete-in-stackframe.html b/browser/devtools/webconsole/test/test-autocomplete-in-stackframe.html
new file mode 100644
index 000000000..ba5212de3
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-autocomplete-in-stackframe.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html dir="ltr" lang="en">
+ <head>
+ <meta charset="utf8">
+ <!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ <title>Test for bug 842682 - use the debugger API for web console autocomplete</title>
+ <script>
+ var foo1 = "globalFoo";
+
+ var foo1Obj = {
+ prop1: "111",
+ prop2: {
+ prop21: "212121"
+ }
+ };
+
+ function firstCall()
+ {
+ var foo2 = "fooFirstCall";
+
+ var foo2Obj = {
+ prop1: {
+ prop11: "111111"
+ }
+ };
+
+ secondCall();
+ }
+
+ function secondCall()
+ {
+ var foo3 = "fooSecondCall";
+
+ var foo3Obj = {
+ prop1: {
+ prop11: "313131"
+ }
+ };
+
+ debugger;
+ }
+ </script>
+ </head>
+ <body>
+ <p>Hello world!</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-585956-console-trace.html b/browser/devtools/webconsole/test/test-bug-585956-console-trace.html
new file mode 100644
index 000000000..e658ba633
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-585956-console-trace.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head><meta charset="utf-8">
+ <title>Web Console test for bug 585956 - console.trace()</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<script type="application/javascript">
+window.foobar585956c = function(a) {
+ console.trace();
+ return a+"c";
+};
+
+function foobar585956b(a) {
+ return foobar585956c(a+"b");
+}
+
+function foobar585956a(omg) {
+ return foobar585956b(omg + "a");
+}
+
+foobar585956a("omg");
+</script>
+ </head>
+ <body>
+ <p>Web Console test for bug 585956 - console.trace().</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-593003-iframe-wrong-hud-iframe.html b/browser/devtools/webconsole/test/test-bug-593003-iframe-wrong-hud-iframe.html
new file mode 100644
index 000000000..ebf9c515f
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-593003-iframe-wrong-hud-iframe.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>WebConsole test: iframe associated to the wrong HUD</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>WebConsole test: iframe associated to the wrong HUD.</p>
+ <p>This is the iframe!</p>
+ </body>
+ </html>
diff --git a/browser/devtools/webconsole/test/test-bug-593003-iframe-wrong-hud.html b/browser/devtools/webconsole/test/test-bug-593003-iframe-wrong-hud.html
new file mode 100644
index 000000000..5b12278d1
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-593003-iframe-wrong-hud.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>WebConsole test: iframe associated to the wrong HUD</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>WebConsole test: iframe associated to the wrong HUD.</p>
+ <iframe
+ src="http://example.com/browser/browser/devtools/webconsole/test/test-bug-593003-iframe-wrong-hud-iframe.html"></iframe>
+ </body>
+ </html>
diff --git a/browser/devtools/webconsole/test/test-bug-595934-canvas-css.html b/browser/devtools/webconsole/test/test-bug-595934-canvas-css.html
new file mode 100644
index 000000000..3c9cf03a5
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-canvas-css.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 595934 - category: CSS Parser (with
+ Canvas)</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script type="text/javascript"
+ src="test-bug-595934-canvas-css.js"></script>
+ </head>
+ <body>
+ <p>Web Console test for bug 595934 - category "CSS Parser" (with
+ Canvas).</p>
+ <p><canvas width="200" height="200">Canvas support is required!</canvas></p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-595934-canvas-css.js b/browser/devtools/webconsole/test/test-bug-595934-canvas-css.js
new file mode 100644
index 000000000..cc364d6a3
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-canvas-css.js
@@ -0,0 +1,10 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+window.addEventListener("DOMContentLoaded", function() {
+ var canvas = document.querySelector("canvas");
+ var context = canvas.getContext("2d");
+ context.strokeStyle = "foobarCanvasCssParser";
+}, false);
diff --git a/browser/devtools/webconsole/test/test-bug-595934-css-loader.css b/browser/devtools/webconsole/test/test-bug-595934-css-loader.css
new file mode 100644
index 000000000..b4224430f
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-css-loader.css
@@ -0,0 +1,10 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+body {
+ color: #0f0;
+ font-weight: bold;
+}
+
diff --git a/browser/devtools/webconsole/test/test-bug-595934-css-loader.css^headers^ b/browser/devtools/webconsole/test/test-bug-595934-css-loader.css^headers^
new file mode 100644
index 000000000..e7be84a71
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-css-loader.css^headers^
@@ -0,0 +1 @@
+Content-Type: image/png
diff --git a/browser/devtools/webconsole/test/test-bug-595934-css-loader.html b/browser/devtools/webconsole/test/test-bug-595934-css-loader.html
new file mode 100644
index 000000000..6bb0d54c5
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-css-loader.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 595934 - category: CSS Loader</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <link rel="stylesheet" href="test-bug-595934-css-loader.css">
+ </head>
+ <body>
+ <p>Web Console test for bug 595934 - category "CSS Loader".</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-595934-css-parser.css b/browser/devtools/webconsole/test/test-bug-595934-css-parser.css
new file mode 100644
index 000000000..f6db82398
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-css-parser.css
@@ -0,0 +1,10 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+p {
+ color: #0f0;
+ foobarCssParser: failure;
+}
+
diff --git a/browser/devtools/webconsole/test/test-bug-595934-css-parser.html b/browser/devtools/webconsole/test/test-bug-595934-css-parser.html
new file mode 100644
index 000000000..a4ea74ba3
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-css-parser.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 595934 - category: CSS Parser</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <link rel="stylesheet" type="text/css"
+ href="test-bug-595934-css-parser.css">
+ </head>
+ <body>
+ <p>Web Console test for bug 595934 - category "CSS Parser".</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-595934-empty-getelementbyid.html b/browser/devtools/webconsole/test/test-bug-595934-empty-getelementbyid.html
new file mode 100644
index 000000000..a70f9011b
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-empty-getelementbyid.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 595934 - category: DOM.
+ (empty getElementById())</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script type="text/javascript"
+ src="test-bug-595934-empty-getelementbyid.js"></script>
+ </head>
+ <body>
+ <p>Web Console test for bug 595934 - category "DOM"
+ (empty getElementById()).</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-595934-empty-getelementbyid.js b/browser/devtools/webconsole/test/test-bug-595934-empty-getelementbyid.js
new file mode 100644
index 000000000..dd94d716d
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-empty-getelementbyid.js
@@ -0,0 +1,8 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+window.addEventListener("load", function() {
+ document.getElementById("");
+}, false);
diff --git a/browser/devtools/webconsole/test/test-bug-595934-html.html b/browser/devtools/webconsole/test/test-bug-595934-html.html
new file mode 100644
index 000000000..fe35afef6
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-html.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 595934 - category: HTML</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>Web Console test for bug 595934 - category "HTML".</p>
+ <form action="?" enctype="multipart/form-data">
+ <p><label>Input <input type="text" value="test value"></label></p>
+ </form>
+ </body>
+</html>
+
diff --git a/browser/devtools/webconsole/test/test-bug-595934-image.html b/browser/devtools/webconsole/test/test-bug-595934-image.html
new file mode 100644
index 000000000..312ecd49f
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-image.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 595934 - category: Image</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>Web Console test for bug 595934 - category Image.</p>
+ <p><img src="test-bug-595934-image.jpg" alt="corrupted image"></p>
+ </body>
+</html>
+
+
diff --git a/browser/devtools/webconsole/test/test-bug-595934-image.jpg b/browser/devtools/webconsole/test/test-bug-595934-image.jpg
new file mode 100644
index 000000000..947e5f11b
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-image.jpg
Binary files differ
diff --git a/browser/devtools/webconsole/test/test-bug-595934-imagemap.html b/browser/devtools/webconsole/test/test-bug-595934-imagemap.html
new file mode 100644
index 000000000..007c3c01b
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-imagemap.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 595934 - category: ImageMap</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>Web Console test for bug 595934 - category "ImageMap".</p>
+ <p><img src="test-image.png" usemap="#testMap" alt="Test image"></p>
+ <map name="testMap">
+ <area shape="rect" coords="0,0,10,10,5" href="#" alt="Test area" />
+ </map>
+ </body>
+</html>
+
diff --git a/browser/devtools/webconsole/test/test-bug-595934-malformedxml-external.html b/browser/devtools/webconsole/test/test-bug-595934-malformedxml-external.html
new file mode 100644
index 000000000..2fd8beac5
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-malformedxml-external.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 595934 - category: malformed-xml.
+ (external file)</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script type="text/javascript"><!--
+ var req = new XMLHttpRequest();
+ req.open("GET", "test-bug-595934-malformedxml-external.xml", true);
+ req.send(null);
+ // --></script>
+ </head>
+ <body>
+ <p>Web Console test for bug 595934 - category "malformed-xml"
+ (external file).</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-595934-malformedxml-external.xml b/browser/devtools/webconsole/test/test-bug-595934-malformedxml-external.xml
new file mode 100644
index 000000000..4812786f1
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-malformedxml-external.xml
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>Web Console test for bug 595934 - category "malformed-xml".</p>
+ </body>
diff --git a/browser/devtools/webconsole/test/test-bug-595934-malformedxml.xhtml b/browser/devtools/webconsole/test/test-bug-595934-malformedxml.xhtml
new file mode 100644
index 000000000..62689c567
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-malformedxml.xhtml
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>Web Console test for bug 595934 - category: malformed-xml</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>Web Console test for bug 595934 - category "malformed-xml".</p>
+ </body>
diff --git a/browser/devtools/webconsole/test/test-bug-595934-svg.xhtml b/browser/devtools/webconsole/test/test-bug-595934-svg.xhtml
new file mode 100644
index 000000000..572382c64
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-svg.xhtml
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>Web Console test for bug 595934 - category: SVG</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>Web Console test for bug 595934 - category "SVG".</p>
+ <svg version="1.1" width="120" height="fooBarSVG"
+ xmlns="http://www.w3.org/2000/svg">
+ <ellipse fill="#0f0" stroke="#000" cx="50%"
+ cy="50%" rx="50%" ry="50%" />
+ </svg>
+ </body>
+</html>
+
diff --git a/browser/devtools/webconsole/test/test-bug-595934-workers.html b/browser/devtools/webconsole/test/test-bug-595934-workers.html
new file mode 100644
index 000000000..baf5a6215
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-workers.html
@@ -0,0 +1,18 @@
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 595934 - category: DOM Worker
+ javascript</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p id="foobar">Web Console test for bug 595934 - category "DOM Worker
+ javascript".</p>
+ <script type="text/javascript">
+ var myWorker = new Worker("test-bug-595934-workers.js");
+ myWorker.postMessage("hello world");
+ </script>
+ </body>
+</html>
+
diff --git a/browser/devtools/webconsole/test/test-bug-595934-workers.js b/browser/devtools/webconsole/test/test-bug-595934-workers.js
new file mode 100644
index 000000000..4e93c967b
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-595934-workers.js
@@ -0,0 +1,9 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+onmessage = function() {
+ fooBarWorker();
+}
+
diff --git a/browser/devtools/webconsole/test/test-bug-597136-external-script-errors.html b/browser/devtools/webconsole/test/test-bug-597136-external-script-errors.html
new file mode 100644
index 000000000..25bdeecc5
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-597136-external-script-errors.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+<!--
+ ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Patrick Walton <pcwalton@mozilla.com>
+ *
+ * ***** END LICENSE BLOCK *****
+ -->
+ <title>Test for bug 597136: external script errors</title>
+ </head>
+ <body>
+ <h1>Test for bug 597136: external script errors</h1>
+ <p><button onclick="f()">Click me</button</p>
+
+ <script type="text/javascript"
+ src="test-bug-597136-external-script-errors.js"></script>
+ </body>
+</html>
+
diff --git a/browser/devtools/webconsole/test/test-bug-597136-external-script-errors.js b/browser/devtools/webconsole/test/test-bug-597136-external-script-errors.js
new file mode 100644
index 000000000..87c0aff8e
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-597136-external-script-errors.js
@@ -0,0 +1,14 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ * Patrick Walton <pcwalton@mozilla.com>
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+function f() {
+ bogus.g();
+}
+
diff --git a/browser/devtools/webconsole/test/test-bug-597756-reopen-closed-tab.html b/browser/devtools/webconsole/test/test-bug-597756-reopen-closed-tab.html
new file mode 100644
index 000000000..68e19e677
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-597756-reopen-closed-tab.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf-8">
+ <title>Bug 597756: test error logging after tab close and reopen</title>
+ <!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ </head>
+ <body>
+ <h1>Bug 597756: test error logging after tab close and reopen.</h1>
+
+ <script type="text/javascript"><!--
+ fooBug597756_error.bar();
+ // --></script>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-599725-response-headers.sjs b/browser/devtools/webconsole/test/test-bug-599725-response-headers.sjs
new file mode 100644
index 000000000..2e78d6b7b
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-599725-response-headers.sjs
@@ -0,0 +1,25 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response)
+{
+ var Etag = '"4c881ab-b03-435f0a0f9ef00"';
+ var IfNoneMatch = request.hasHeader("If-None-Match")
+ ? request.getHeader("If-None-Match")
+ : "";
+
+ var page = "<!DOCTYPE html><html><body><p>hello world!</p></body></html>";
+
+ response.setHeader("Etag", Etag, false);
+
+ if (IfNoneMatch == Etag) {
+ response.setStatusLine(request.httpVersion, "304", "Not Modified");
+ }
+ else {
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Length", page.length + "", false);
+ response.write(page);
+ }
+}
diff --git a/browser/devtools/webconsole/test/test-bug-600183-charset.html b/browser/devtools/webconsole/test/test-bug-600183-charset.html
new file mode 100644
index 000000000..040490a6b
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-600183-charset.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+ <meta charset="gb2312">
+ <title>Console HTTP test page (chinese)</title>
+ </head>
+ <body>
+ <p>µÄÎʺò!</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-600183-charset.html^headers^ b/browser/devtools/webconsole/test/test-bug-600183-charset.html^headers^
new file mode 100644
index 000000000..9f3e2302f
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-600183-charset.html^headers^
@@ -0,0 +1 @@
+Content-Type: text/html; charset=gb2312
diff --git a/browser/devtools/webconsole/test/test-bug-601177-log-levels.html b/browser/devtools/webconsole/test/test-bug-601177-log-levels.html
new file mode 100644
index 000000000..a59213907
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-601177-log-levels.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 601177: log levels</title>
+ <script src="test-bug-601177-log-levels.js" type="text/javascript"></script>
+ <script type="text/javascript"><!--
+ window.undefinedPropertyBug601177;
+ // --></script>
+ <!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ </head>
+ <body>
+ <h1>Web Console test for bug 601177: log levels</h1>
+ <img src="test-image.png?bug601177">
+ <img src="foobar-known-to-fail.png?bug601177">
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-601177-log-levels.js b/browser/devtools/webconsole/test/test-bug-601177-log-levels.js
new file mode 100644
index 000000000..ea37f533d
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-601177-log-levels.js
@@ -0,0 +1,8 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+foobarBug601177strictError = "strict error";
+
+window.foobarBug601177exception();
diff --git a/browser/devtools/webconsole/test/test-bug-603750-websocket.html b/browser/devtools/webconsole/test/test-bug-603750-websocket.html
new file mode 100644
index 000000000..f0097dd77
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-603750-websocket.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 603750 - Web Socket errors</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>Web Console test for bug 595934 - Web Socket errors.</p>
+ <iframe src="data:text/html;charset=utf-8,hello world!"></iframe>
+ <script type="text/javascript" src="test-bug-603750-websocket.js"></script>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-603750-websocket.js b/browser/devtools/webconsole/test/test-bug-603750-websocket.js
new file mode 100644
index 000000000..3746424cc
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-603750-websocket.js
@@ -0,0 +1,18 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+window.addEventListener("load", function () {
+ var ws1 = new WebSocket("ws://0.0.0.0:81");
+ ws1.onopen = function() {
+ ws1.send("test 1");
+ ws1.close();
+ };
+
+ var ws2 = new window.frames[0].WebSocket("ws://0.0.0.0:82");
+ ws2.onopen = function() {
+ ws2.send("test 2");
+ ws2.close();
+ };
+}, false);
diff --git a/browser/devtools/webconsole/test/test-bug-609872-cd-iframe-child.html b/browser/devtools/webconsole/test/test-bug-609872-cd-iframe-child.html
new file mode 100644
index 000000000..451eba21e
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-609872-cd-iframe-child.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>test for bug 609872 - iframe child</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>test for bug 609872 - iframe child</p>
+ <script>window.foobarBug609872 = 'child!';</script>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-609872-cd-iframe-parent.html b/browser/devtools/webconsole/test/test-bug-609872-cd-iframe-parent.html
new file mode 100644
index 000000000..fdb636b97
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-609872-cd-iframe-parent.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>test for bug 609872 - iframe parent</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>test for bug 609872 - iframe parent</p>
+ <script>window.foobarBug609872 = 'parent!';</script>
+ <iframe src="test-bug-609872-cd-iframe-child.html"></iframe>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-613013-console-api-iframe.html b/browser/devtools/webconsole/test/test-bug-613013-console-api-iframe.html
new file mode 100644
index 000000000..edf40e80e
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-613013-console-api-iframe.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>test for bug 613013</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>test for bug 613013</p>
+ <script type="text/javascript"><!--
+ (function () {
+ var iframe = document.createElement('iframe');
+ iframe.src = 'data:text/html;charset=utf-8,little iframe';
+ document.body.appendChild(iframe);
+
+ console.log("foobarBug613013");
+ })();
+ // --></script>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-618078-network-exceptions.html b/browser/devtools/webconsole/test/test-bug-618078-network-exceptions.html
new file mode 100644
index 000000000..ac755e1b9
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-618078-network-exceptions.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 618078 - exception in async network request
+ callback</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script type="text/javascript">
+ var req = new XMLHttpRequest();
+ req.open('GET', 'http://example.com', true);
+ req.onreadystatechange = function() {
+ if (req.readyState == 4) {
+ bug618078exception();
+ }
+ };
+ req.send(null);
+ </script>
+ </head>
+ <body>
+ <p>Web Console test for bug 618078 - exception in async network request
+ callback.</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-621644-jsterm-dollar.html b/browser/devtools/webconsole/test/test-bug-621644-jsterm-dollar.html
new file mode 100644
index 000000000..09c986703
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-621644-jsterm-dollar.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 621644</title>
+ <script>
+ function $(elem) {
+ return elem.innerHTML;
+ }
+ function $$(doc) {
+ return doc.title;
+ }
+ </script>
+ <!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ </head>
+ <body>
+ <h1>Web Console test for bug 621644</h1>
+ <p>hello world!</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-630733-response-redirect-headers.sjs b/browser/devtools/webconsole/test/test-bug-630733-response-redirect-headers.sjs
new file mode 100644
index 000000000..f92e0fe65
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-630733-response-redirect-headers.sjs
@@ -0,0 +1,16 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(request, response)
+{
+ var page = "<!DOCTYPE html><html><body><p>hello world! bug 630733</p></body></html>";
+
+ response.setStatusLine(request.httpVersion, "301", "Moved Permanently");
+ response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Length", page.length + "", false);
+ response.setHeader("x-foobar-bug630733", "bazbaz", false);
+ response.setHeader("Location", "/redirect-from-bug-630733", false);
+ response.write(page);
+}
diff --git a/browser/devtools/webconsole/test/test-bug-632275-getters.html b/browser/devtools/webconsole/test/test-bug-632275-getters.html
new file mode 100644
index 000000000..349c301f3
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-632275-getters.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 632275 - getters</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<script type="application/javascript;version=1.8">
+ document.foobar = {
+ _val: 5,
+ get val() { return ++this._val; }
+ };
+</script>
+
+ </head>
+ <body>
+ <p>Web Console test for bug 632275 - getters.</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-632347-iterators-generators.html b/browser/devtools/webconsole/test/test-bug-632347-iterators-generators.html
new file mode 100644
index 000000000..a6080c642
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-632347-iterators-generators.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 632347 - iterators and generators</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<script type="application/javascript;version=1.8">
+(function(){
+function genFunc() {
+ var a = 5;
+ while (a < 10) {
+ yield a++;
+ }
+}
+
+window._container = {};
+
+_container.gen1 = genFunc();
+_container.gen1.next();
+
+var obj = { foo: "bar", baz: "baaz", hay: "stack" };
+_container.iter1 = Iterator(obj);
+
+function Range(low, high) {
+ this.low = low;
+ this.high = high;
+}
+
+function RangeIterator(range) {
+ this.range = range;
+ this.current = this.range.low;
+}
+
+RangeIterator.prototype.next = function() {
+ if (this.current > this.range.high) {
+ throw StopIteration;
+ } else {
+ return this.current++;
+ }
+}
+
+Range.prototype.__iterator__ = function() {
+ return new RangeIterator(this);
+}
+
+_container.iter2 = new Range(3, 15);
+
+_container.gen2 = (i * 2 for (i in _container.iter2));
+})();
+</script>
+ </head>
+ <body>
+ <p>Web Console test for bug 632347 - iterators and generators.</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-644419-log-limits.html b/browser/devtools/webconsole/test/test-bug-644419-log-limits.html
new file mode 100644
index 000000000..21d99ba14
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-644419-log-limits.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for bug 644419: console log limits</title>
+ </head>
+ <body>
+ <h1>Test for bug 644419: Console should have user-settable log limits for
+ each message category</h1>
+
+ <script type="text/javascript">
+ function foo() {
+ bar.baz();
+ }
+ foo();
+ </script>
+ </body>
+</html>
+
diff --git a/browser/devtools/webconsole/test/test-bug-646025-console-file-location.html b/browser/devtools/webconsole/test/test-bug-646025-console-file-location.html
new file mode 100644
index 000000000..7c80f1446
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-646025-console-file-location.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+ <meta charset="utf-8">
+ <title>Console file location test</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script src="test-file-location.js"></script>
+ </head>
+ <body>
+ <h1>Web Console File Location Test Page</h1>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-658368-time-methods.html b/browser/devtools/webconsole/test/test-bug-658368-time-methods.html
new file mode 100644
index 000000000..cc50b6313
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-658368-time-methods.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for bug 658368: Expand console object with time and timeEnd
+ methods</title>
+ </head>
+ <body>
+ <h1>Test for bug 658368: Expand console object with time and timeEnd
+ methods</h1>
+
+ <script type="text/javascript">
+ function foo() {
+ console.timeEnd("aTimer");
+ }
+ console.time("aTimer");
+ foo();
+ console.time("bTimer");
+ </script>
+ </body>
+</html>
+
diff --git a/browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html b/browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html
new file mode 100644
index 000000000..db83274f0
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+ <meta charset="utf8">
+ <title>Mixed Content test - http on https</title>
+ <script src="testscript.js"></script>
+ <!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ </head>
+ <body>
+ <iframe src = "http://example.com"></iframe>
+ </body>
+</html>
+
diff --git a/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-inner.html b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-inner.html
new file mode 100644
index 000000000..ccb363ed9
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-inner.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Bug 752559 - print warning to error console when iframe sandbox
+ is being used ineffectively</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>I am sandboxed and want to escape.</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-nested1.html b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-nested1.html
new file mode 100644
index 000000000..273e1a4e5
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-nested1.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Bug 752559 - print warning to error console when iframe sandbox
+ is being used ineffectively</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <iframe
+src="http://www.example.com/browser/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-inner.html"></iframe>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-nested2.html b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-nested2.html
new file mode 100644
index 000000000..21f5dd672
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-nested2.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Bug 752559 - print warning to error console when iframe sandbox
+ is being used ineffectively</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <iframe
+src="http://www.example.com/browser/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-inner.html" sandbox="allow-scripts allow-same-origin"></iframe>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning0.html b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning0.html
new file mode 100644
index 000000000..233a6cb70
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning0.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Bug 752559 - print warning to error console when iframe sandbox
+ is being used ineffectively (allow-scripts, allow-same-origin)</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <iframe src="test-bug-752559-ineffective-iframe-sandbox-warning-inner.html" sandbox="allow-scripts allow-same-origin"></iframe>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning1.html b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning1.html
new file mode 100644
index 000000000..da0d58819
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning1.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Bug 752559 - print warning to error console when iframe sandbox
+ is being used ineffectively (allow-scripts, no allow-same-origin)</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <iframe src="test-bug-752559-ineffective-iframe-sandbox-warning-inner.html" sandbox="allow-scripts"></iframe>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning2.html b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning2.html
new file mode 100644
index 000000000..f33f0a6dc
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning2.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Bug 752559 - print warning to error console when iframe sandbox
+ is being used ineffectively (no allow-scripts, allow-same-origin)</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <iframe src="test-bug-752559-ineffective-iframe-sandbox-warning-inner.html" sandbox="allow-same-origin"></iframe>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning3.html b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning3.html
new file mode 100644
index 000000000..1d8f5ac47
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning3.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Bug 752559 - print warning to error console when iframe sandbox
+ is being used ineffectively (allow-scripts, allow-same-origin)</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <iframe
+src="http://www.example.com/browser/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-inner.html" sandbox="allow-scripts allow-same-origin"></iframe>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning4.html b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning4.html
new file mode 100644
index 000000000..7c749b8c7
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning4.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Bug 752559 - print warning to error console when iframe sandbox
+ is being used ineffectively (allow-scripts, allow-same-origin, nested)</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <iframe
+src="http://www.example.com/browser/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-nested1.html" sandbox="allow-scripts allow-same-origin"></iframe>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning5.html b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning5.html
new file mode 100644
index 000000000..7aad0b2c5
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning5.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Bug 752559 - print warning to error console when iframe sandbox
+ is being used ineffectively (nested, allow-scripts, allow-same-origin)</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <iframe
+src="http://www.example.com/browser/browser/devtools/webconsole/test/test-bug-752559-ineffective-iframe-sandbox-warning-nested2.html"></iframe>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-762593-insecure-passwords-about-blank-web-console-warning.html b/browser/devtools/webconsole/test/test-bug-762593-insecure-passwords-about-blank-web-console-warning.html
new file mode 100644
index 000000000..d7bcd45d6
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-762593-insecure-passwords-about-blank-web-console-warning.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Bug 762593 - Add warning/error Message to Web Console when the
+ page includes Insecure Password fields</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+ <!-- This test tests the scenario where a javascript adds password fields to
+ an about:blank iframe inside an insecure web page. It ensures that
+ insecure password fields like those are detected and a warning is sent to
+ the web console. -->
+ </head>
+ <body>
+ <p>This insecure page is served with an about:blank iframe. A script then adds a
+ password field to it.</p>
+ <iframe id = "myiframe" width = "300" height="300" >
+ </iframe>
+ <script>
+ var doc = window.document;
+ var myIframe = doc.getElementById("myiframe");
+ myIframe.contentDocument.open();
+ myIframe.contentDocument.write("<form><input type = 'password' name='pwd' value='test'> </form>");
+ myIframe.contentDocument.close();
+ </script>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-762593-insecure-passwords-web-console-warning.html b/browser/devtools/webconsole/test/test-bug-762593-insecure-passwords-web-console-warning.html
new file mode 100644
index 000000000..a4d0d7843
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-762593-insecure-passwords-web-console-warning.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf8">
+ <title>Bug 762593 - Add warning/error Message to Web Console when the
+ page includes Insecure Password fields</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>This page is served with an iframe with insecure password field.</p>
+ <iframe src
+ ="http://example.com/browser/browser/devtools/webconsole/test/test-iframe-762593-insecure-frame.html">
+ </iframe>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-766001-console-log.js b/browser/devtools/webconsole/test/test-bug-766001-console-log.js
new file mode 100644
index 000000000..8f6a3bb9c
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-766001-console-log.js
@@ -0,0 +1,10 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function onLoad123() {
+ console.log("Blah Blah");
+}
+
+window.addEventListener("load", onLoad123, false);
diff --git a/browser/devtools/webconsole/test/test-bug-766001-js-console-links.html b/browser/devtools/webconsole/test/test-bug-766001-js-console-links.html
new file mode 100644
index 000000000..6a6ac6008
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-766001-js-console-links.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 766001 : Open JS/Console call Links in Debugger</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script type="text/javascript" src="test-bug-766001-js-errors.js"></script>
+ <script type="text/javascript" src="test-bug-766001-console-log.js"></script>
+ </head>
+ <body>
+ <p>Web Console test for bug 766001 : Open JS/Console call Links in Debugger.</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-766001-js-errors.js b/browser/devtools/webconsole/test/test-bug-766001-js-errors.js
new file mode 100644
index 000000000..932204395
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-766001-js-errors.js
@@ -0,0 +1,7 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+window.addEventListener("load", function() {
+ document.bar();
+}, false);
diff --git a/browser/devtools/webconsole/test/test-bug-782653-css-errors-1.css b/browser/devtools/webconsole/test/test-bug-782653-css-errors-1.css
new file mode 100644
index 000000000..ad7fd1999
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-782653-css-errors-1.css
@@ -0,0 +1,10 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+body {
+ color: #0f0;
+ font-weight: green;
+}
+
diff --git a/browser/devtools/webconsole/test/test-bug-782653-css-errors-2.css b/browser/devtools/webconsole/test/test-bug-782653-css-errors-2.css
new file mode 100644
index 000000000..91b14137a
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-782653-css-errors-2.css
@@ -0,0 +1,10 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+body {
+ color: #0fl;
+ font-weight: bold;
+}
+
diff --git a/browser/devtools/webconsole/test/test-bug-782653-css-errors.html b/browser/devtools/webconsole/test/test-bug-782653-css-errors.html
new file mode 100644
index 000000000..7ca11fc34
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-782653-css-errors.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 782653 : Open CSS Links in Style Editor</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <link rel="stylesheet" href="test-bug-782653-css-errors-1.css">
+ <link rel="stylesheet" href="test-bug-782653-css-errors-2.css">
+ </head>
+ <body>
+ <p>Web Console test for bug 782653 : Open CSS Links in Style Editor.</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-837351-security-errors.html b/browser/devtools/webconsole/test/test-bug-837351-security-errors.html
new file mode 100644
index 000000000..db83274f0
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-837351-security-errors.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+ <meta charset="utf8">
+ <title>Mixed Content test - http on https</title>
+ <script src="testscript.js"></script>
+ <!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ </head>
+ <body>
+ <iframe src = "http://example.com"></iframe>
+ </body>
+</html>
+
diff --git a/browser/devtools/webconsole/test/test-bug-846918-hsts-invalid-headers.html b/browser/devtools/webconsole/test/test-bug-846918-hsts-invalid-headers.html
new file mode 100644
index 000000000..a2353354d
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-846918-hsts-invalid-headers.html
@@ -0,0 +1,13 @@
+<!doctype html>
+ <html>
+ <head>
+ <meta charset="utf8">
+ <title>Bug 846918 - Report invalid strict-transport-security
+ headers to the web console</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>This page is served with an invalid STS header.</p>
+ </body>
+ </html>
diff --git a/browser/devtools/webconsole/test/test-bug-846918-hsts-invalid-headers.html^headers^ b/browser/devtools/webconsole/test/test-bug-846918-hsts-invalid-headers.html^headers^
new file mode 100644
index 000000000..9778993d7
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-846918-hsts-invalid-headers.html^headers^
@@ -0,0 +1 @@
+Strict-Transport-Security: max-age444 \ No newline at end of file
diff --git a/browser/devtools/webconsole/test/test-bug-859170-longstring-hang.html b/browser/devtools/webconsole/test/test-bug-859170-longstring-hang.html
new file mode 100644
index 000000000..51bc0de28
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-859170-longstring-hang.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head><meta charset="utf-8">
+ <title>Web Console test for bug 859170 - very long strings hang the browser</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<script type="application/javascript">
+(function() {
+var longString = "abbababazomglolztest";
+for (var i = 0; i < 10; i++) {
+ longString += longString + longString;
+}
+
+longString = "foobar" + (new Array(9000)).join("a") + "foobaz" +
+ longString + "boom!";
+console.log(longString);
+})();
+</script>
+ </head>
+ <body>
+ <p>Web Console test for bug 859170 - very long strings hang the browser.</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-869003-iframe.html b/browser/devtools/webconsole/test/test-bug-869003-iframe.html
new file mode 100644
index 000000000..5a29728e5
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-869003-iframe.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 869003</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script type="text/javascript"><!--
+ window.onload = function testConsoleLogging()
+ {
+ var o = { hello: "world!", bug: 869003 };
+ console.log("foobar", o);
+ };
+ // --></script>
+ </head>
+ <body>
+ <p>Make sure users can inspect objects from cross-domain iframes.</p>
+ <p>Iframe window.</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-869003-top-window.html b/browser/devtools/webconsole/test/test-bug-869003-top-window.html
new file mode 100644
index 000000000..ab3b87542
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-869003-top-window.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 869003</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>Make sure users can inspect objects from cross-domain iframes.</p>
+ <p>Top window.</p>
+ <iframe src="http://example.org/browser/browser/devtools/webconsole/test/test-bug-869003-iframe.html"></iframe>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug-952277-highlight-nodes-in-vview.html b/browser/devtools/webconsole/test/test-bug-952277-highlight-nodes-in-vview.html
new file mode 100644
index 000000000..de297d9b5
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-952277-highlight-nodes-in-vview.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 952277 - Highlighting and selecting nodes from the variablesview</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>Web Console test for bug 952277 - Highlighting and selecting nodes from the variablesview</p>
+ <p>Web Console test for bug 952277 - Highlighting and selecting nodes from the variablesview</p>
+ <p>Web Console test for bug 952277 - Highlighting and selecting nodes from the variablesview</p>
+ </body>
+</html>
+
diff --git a/browser/devtools/webconsole/test/test-bug-989025-iframe-parent.html b/browser/devtools/webconsole/test/test-bug-989025-iframe-parent.html
new file mode 100644
index 000000000..e9a8553dd
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug-989025-iframe-parent.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>test for bug 989025 - iframe parent</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>test for bug 989025 - iframe parent</p>
+ <iframe src="http://mochi.test:8888/browser/browser/devtools/webconsole/test/test-bug-609872-cd-iframe-child.html"></iframe>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug_923281_console_log_filter.html b/browser/devtools/webconsole/test/test-bug_923281_console_log_filter.html
new file mode 100644
index 000000000..f2d650a5d
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug_923281_console_log_filter.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf-8">
+ <title>Console test</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script type="text/javascript" src="test-bug_923281_test1.js"></script>
+ <script type="text/javascript" src="test-bug_923281_test2.js"></script>
+ </head>
+ <body></body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-bug_923281_test1.js b/browser/devtools/webconsole/test/test-bug_923281_test1.js
new file mode 100644
index 000000000..81342a437
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug_923281_test1.js
@@ -0,0 +1,5 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+console.log("Sample log.");
+console.log("This log should be filtered when filtered for test2.js.");
diff --git a/browser/devtools/webconsole/test/test-bug_923281_test2.js b/browser/devtools/webconsole/test/test-bug_923281_test2.js
new file mode 100644
index 000000000..f523103cd
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug_923281_test2.js
@@ -0,0 +1,4 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+console.log("This is a random text.");
diff --git a/browser/devtools/webconsole/test/test-bug_939783_console_trace_duplicates.html b/browser/devtools/webconsole/test/test-bug_939783_console_trace_duplicates.html
new file mode 100644
index 000000000..ab44de09f
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug_939783_console_trace_duplicates.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test for bug 939783 - different console.trace() calls
+ wrongly filtered as duplicates</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<script type="application/javascript">
+function foo1() {
+ foo2();
+}
+
+function foo1b() {
+ foo2();
+}
+
+function foo2() {
+ foo3();
+}
+
+function foo3() {
+ console.trace();
+}
+
+foo1(); foo1();
+foo1b();
+
+</script>
+ </head>
+ <body>
+ <p>Web Console test for bug 939783 - different console.trace() calls
+ wrongly filtered as duplicates</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-certificate-messages.html b/browser/devtools/webconsole/test/test-certificate-messages.html
new file mode 100644
index 000000000..b0419a6fc
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-certificate-messages.html
@@ -0,0 +1,22 @@
+<!--
+ Bug 1068949 - Log crypto warnings to the security pane in the webconsole
+-->
+
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ <title>Security warning test - no violations</title>
+ <!-- ensure no subresource errors so window re-use doesn't cause failures -->
+ <link rel="icon" href="data:;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4//8/AAX+Av7czFnnAAAAAElFTkSuQmCC">
+ <script>
+ console.log("If you haven't seen ssl warnings yet, you won't");
+ </script>
+ <!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-closure-optimized-out.html b/browser/devtools/webconsole/test/test-closure-optimized-out.html
new file mode 100644
index 000000000..3ad4e8fc0
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-closure-optimized-out.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'/>
+ <title>Debugger Test for Inspecting Optimized-Out Variables</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script type="text/javascript">
+ window.addEventListener("load", function onload() {
+ window.removeEventListener("load", onload);
+ function clickHandler(event) {
+ button.removeEventListener("click", clickHandler, false);
+ function outer(arg) {
+ var upvar = arg * 2;
+ // The inner lambda only aliases arg, so the frontend alias analysis decides
+ // that upvar is not aliased and is not in the CallObject.
+ return function () {
+ arg += 2;
+ };
+ }
+
+ var f = outer(42);
+ f();
+ }
+ var button = document.querySelector("button");
+ button.addEventListener("click", clickHandler, false);
+ });
+ </script>
+
+ </head>
+ <body>
+ <button>Click me!</button>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-closures.html b/browser/devtools/webconsole/test/test-closures.html
new file mode 100644
index 000000000..4fadade20
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-closures.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset='utf-8'/>
+ <title>Console Test for Closure Inspection</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script type="text/javascript">
+ function injectPerson() {
+ var PersonFactory = function _pfactory(name) {
+ var foo = 10;
+ return {
+ getName: function() { return name; },
+ getFoo: function() { foo = Date.now(); return foo; }
+ };
+ };
+ window.george = new PersonFactory("George");
+ debugger;
+ }
+ </script>
+
+ </head>
+ <body>
+ <button onclick="injectPerson()">Test</button>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-console-api-stackframe.html b/browser/devtools/webconsole/test/test-console-api-stackframe.html
new file mode 100644
index 000000000..df7fef9b1
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-api-stackframe.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html dir="ltr" lang="en">
+ <head>
+ <meta charset="utf8">
+ <!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ <title>Test for bug 920116 - stacktraces for console API messages</title>
+ <script>
+ function firstCall() {
+ secondCall();
+ }
+
+ function secondCall() {
+ thirdCall();
+ }
+
+ function thirdCall() {
+ console.log("foo-log");
+ console.error("foo-error");
+ console.exception("foo-exception");
+ console.assert("red" == "blue", "foo-assert");
+ }
+
+ window.onload = firstCall;
+ </script>
+ </head>
+ <body>
+ <p>Hello world!</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-console-assert.html b/browser/devtools/webconsole/test/test-console-assert.html
new file mode 100644
index 000000000..b104d72d4
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-assert.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ <meta charset="utf-8">
+ <title>console.assert() test</title>
+ <script type="text/javascript">
+ function test() {
+ console.log("start");
+ console.assert(false, "false assert");
+ console.assert(0, "falsy assert");
+ console.assert(true, "true assert");
+ console.log("end");
+ }
+ </script>
+ </head>
+ <body>
+ <p>test console.assert()</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-console-column.html b/browser/devtools/webconsole/test/test-console-column.html
new file mode 100644
index 000000000..ff9cc81e1
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-column.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <meta charset="utf-8">
+ <title>Console test</title>
+
+ <script type="text/javascript">
+ console.info("INLINE SCRIPT:"); console.log('Further');
+ console.warn("I'm warning you, he will eat up all yr bacon.");
+ console.error("Error Message");
+ console.log('Rainbooooww');
+ console.log('NYAN CATZ');
+ </script>
+ </head>
+</html>
diff --git a/browser/devtools/webconsole/test/test-console-count-external-file.js b/browser/devtools/webconsole/test/test-console-count-external-file.js
new file mode 100644
index 000000000..77959b831
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-count-external-file.js
@@ -0,0 +1,7 @@
+function counterExternalFile() {
+ console.count("console.count() testcounter");
+}
+function externalCountersWithoutLabel() {
+ console.count();
+ console.count();
+}
diff --git a/browser/devtools/webconsole/test/test-console-count.html b/browser/devtools/webconsole/test/test-console-count.html
new file mode 100644
index 000000000..e6db0ebb0
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-count.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ <meta charset="utf-8">
+ <title>console.count() test</title>
+ <script src="test-console-count-external-file.js"></script>
+ <script tyoe="text/javascript">
+ function counterSeperateScriptTag() {
+ console.count("console.count() testcounter");
+ }
+ </script>
+ <script type="text/javascript">
+ function counterNoLabel() {
+ console.count();
+ }
+ function countersWithoutLabel() {
+ console.count();
+ console.count();
+ }
+ function counterWithLabel() {
+ console.count("console.count() testcounter");
+ }
+ function testLocal() {
+ console.log("start");
+ counterNoLabel();
+ counterNoLabel();
+ countersWithoutLabel();
+ counterWithLabel();
+ counterWithLabel();
+ counterSeperateScriptTag();
+ counterSeperateScriptTag();
+ console.log("end");
+ }
+ function testExternal() {
+ console.log("start");
+ counterExternalFile();
+ counterExternalFile();
+ externalCountersWithoutLabel();
+ console.log("end");
+ }
+ </script>
+ </head>
+ <body>
+ <p>test console.count()</p>
+ <button id="local" onclick="testLocal();">
+ test local console.count() calls
+ </button>
+ <button id="external" onclick="testExternal();">
+ test external console.count() calls
+ </button>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-console-extras.html b/browser/devtools/webconsole/test/test-console-extras.html
new file mode 100644
index 000000000..ae0b521c5
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-extras.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+ <meta charset="utf-8">
+ <title>Console extended API test</title>
+ <script type="text/javascript">
+ function test() {
+ console.log("start");
+ console.clear()
+ console.dirxml()
+ console.log("end");
+ }
+ </script>
+ </head>
+ <body>
+ <h1 id="header">Heads Up Display Demo</h1>
+ <button onclick="test();">Test Extended API</button>
+ <div id="myDiv"></div>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-console-output-02.html b/browser/devtools/webconsole/test/test-console-output-02.html
new file mode 100644
index 000000000..c2fe21233
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-output-02.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html dir="ltr" lang="en-US">
+<head>
+ <meta charset="utf-8">
+ <title>Test the web console output - 02</title>
+ <!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+</head>
+<body>
+ <p>hello world!</p>
+ <script type="text/javascript">
+function testfn1() { return 42; }
+
+var testobj1 = {
+ testfn2: function() { return 42; },
+};
+
+function testfn3() { return 42; }
+testfn3.displayName = "testfn3DisplayName";
+
+var array1 = [1, 2, 3, "a", "b", "c", "4", "5"];
+
+var array2 = ["a", document, document.body, document.body.dataset,
+ document.body.classList];
+
+var array3 = [1, window, null, "a", "b", undefined, false, "", -Infinity, testfn3, testobj1, "foo", "bar"];
+
+var array4 = new Array(5);
+array4.push("test");
+array4.push(array4);
+
+var typedarray1 = new Int32Array([1, 287, 8651, 40983, 8754]);
+
+var set1 = new Set([1, 2, null, array3, "a", "b", undefined, document.head]);
+set1.add(set1);
+
+var testobj2 = {a: "b", c: "d", e: 1, f: "2"};
+testobj2.foo = testobj1;
+testobj2.bar = testobj2;
+Object.defineProperty(testobj2, "getterTest", {
+ enumerable: true,
+ get: function() {
+ return 42;
+ },
+});
+
+var testobj3 = {a: "b", c: "d", e: 1, f: "2", g: true, h: null, i: undefined,
+ j: "", k: document.styleSheets, l: document.body.childNodes,
+ o: new Array(125), m: document.head};
+
+var testobj4 = {a: "b", c: "d"};
+Object.defineProperty(testobj4, "nonEnumerable", { value: "hello world" });
+
+var map1 = new Map([["a", "b"], [document.body.children, testobj2]]);
+map1.set(map1, set1);
+
+ </script>
+</body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-console-output-03.html b/browser/devtools/webconsole/test/test-console-output-03.html
new file mode 100644
index 000000000..9dcf051a6
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-output-03.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html dir="ltr" lang="en-US">
+<head>
+ <meta charset="utf-8">
+ <title>Test the web console output - 03</title>
+ <!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+</head>
+<body>
+ <p>hello world!</p>
+ <script type="text/javascript">
+function testBodyClassName() {
+ document.body.className = "test1 tezt2";
+ return document.body;
+}
+
+function testBodyID() {
+ document.body.id = 'foobarid';
+ return document.body;
+}
+
+function testBodyDataset() {
+ document.body.dataset.preview = 'zuzu"<a>foo';
+ return document.body;
+}
+ </script>
+</body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-console-output-04.html b/browser/devtools/webconsole/test/test-console-output-04.html
new file mode 100644
index 000000000..bb4345277
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-output-04.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html dir="ltr" lang="en-US">
+<head>
+ <meta charset="utf-8">
+ <title>Test the web console output - 04</title>
+ <!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+</head>
+<body>
+ <p>hello world!</p>
+ <script type="text/javascript">
+function testTextNode() {
+ return document.querySelector("p").childNodes[0];
+}
+
+function testCommentNode() {
+ return document.head.childNodes[5];
+}
+
+function testDocumentFragment() {
+ var frag = document.createDocumentFragment();
+
+ var div = document.createElement("div");
+ div.id = "foo1";
+ div.className = "bar";
+ frag.appendChild(div);
+
+ var span = document.createElement("span");
+ span.id = "foo2";
+ span.textContent = "hello world";
+ div.appendChild(span);
+
+ var div2 = document.createElement("div");
+ div2.id = "foo3";
+ frag.appendChild(div2);
+
+ return frag;
+}
+
+function testError() {
+ try {
+ window.foobar("a");
+ } catch (ex) {
+ return ex;
+ }
+ return null;
+}
+
+function testDOMException() {
+ try {
+ var foo = document.querySelector("foo;()bar!");
+ } catch (ex) {
+ return ex;
+ }
+ return null;
+}
+
+function testCSSStyleDeclaration() {
+ document.body.style = 'color: green; font-size: 2em';
+ return document.body.style;
+}
+
+function testStyleSheetList() {
+ var style = document.querySelector("style");
+ if (!style) {
+ style = document.createElement("style");
+ style.textContent = "p, div { color: blue; font-weight: bold }\n" +
+ "@media print { p { background-color: yellow } }";
+ document.head.appendChild(style);
+ }
+ return document.styleSheets;
+}
+ </script>
+</body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-console-output-dom-elements.html b/browser/devtools/webconsole/test/test-console-output-dom-elements.html
new file mode 100644
index 000000000..84ec58a7b
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-output-dom-elements.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html dir="ltr" lang="en-US">
+<head>
+ <meta charset="utf-8">
+ <title>Test the web console output - 05</title>
+ <!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+</head>
+<body class="body-class" id="body-id">
+ <p some-attribute="some-value">hello world!</p>
+ <iframe src="data:text/html,<p>hello from iframe</p>"></iframe>
+ <div class="some classname here with more classnames here"></div>
+ <script type="text/javascript">
+function testBodyNode() {
+ return document.body;
+}
+
+function testDocumentElement() {
+ return document.documentElement;
+}
+
+function testDocument() {
+ return document;
+}
+
+function testNode() {
+ return document.querySelector("p");
+}
+
+function testNodeList() {
+ return document.querySelectorAll("*");
+}
+
+function testNodeInIframe() {
+ return document.querySelector("iframe").contentWindow.document.querySelector("p");
+}
+
+function testDocumentFragment() {
+ var frag = document.createDocumentFragment();
+
+ var span = document.createElement("span");
+ span.className = 'foo';
+ span.dataset.lolz = 'hehe';
+
+ var div = document.createElement('div')
+ div.id = 'fragdiv';
+
+ frag.appendChild(span);
+ frag.appendChild(div);
+
+ return frag;
+}
+
+function testNodeInDocumentFragment() {
+ var frag = testDocumentFragment();
+ return frag.firstChild;
+}
+
+function testUnattachedNode() {
+ var p = document.createElement("p");
+ p.className = "such-class";
+ p.dataset.data = "such-data";
+ return p;
+}
+ </script>
+</body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-console-output-events.html b/browser/devtools/webconsole/test/test-console-output-events.html
new file mode 100644
index 000000000..908a86fab
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-output-events.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html dir="ltr" lang="en-US">
+<head>
+ <meta charset="utf-8">
+ <title>Test the web console output for DOM events</title>
+ <!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+</head>
+<body>
+ <p>hello world!</p>
+
+ <script type="text/javascript">
+function testDOMEvents() {
+ function eventLogger(ev) {
+ console.log("eventLogger", ev);
+ }
+ document.addEventListener("mousemove", eventLogger);
+ document.addEventListener("keypress", eventLogger);
+
+ synthesizeMouseMove();
+ synthesizeKeyPress("a", {shiftKey: true});
+}
+
+function synthesizeMouseMove(element) {
+ var mouseEvent = document.createEvent("MouseEvent");
+ mouseEvent.initMouseEvent("mousemove", true, true, window, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null);
+
+ document.dispatchEvent(mouseEvent);
+}
+
+function synthesizeKeyPress(key, options) {
+ var keyboardEvent = document.createEvent("KeyboardEvent");
+ keyboardEvent.initKeyEvent("keypress", true, true, window, false, false,
+ options.shiftKey, false, key.charCodeAt(0), 0);
+ document.dispatchEvent(keyboardEvent);
+}
+ </script>
+</body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-console-replaced-api.html b/browser/devtools/webconsole/test/test-console-replaced-api.html
new file mode 100644
index 000000000..2b05d023a
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-replaced-api.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+ <meta charset="utf-8">
+ <title>Console test replaced API</title>
+ </head>
+ <body>
+ <h1 id="header">Web Console Replace API Test</h1>
+ <script type="text/javascript">
+ window.console = {log: function (msg){}, info: function (msg){}, warn: function (msg){}, error: function (msg){}};
+ </script>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-console-table.html b/browser/devtools/webconsole/test/test-console-table.html
new file mode 100644
index 000000000..7a3f2333e
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console-table.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html dir="ltr" lang="en">
+ <head>
+ <meta charset="utf8">
+ <!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ <title>Test for Bug 899753 - console.table support</title>
+ <script>
+ var languages1 = [
+ { name: "JavaScript", fileExtension: [".js"] },
+ { name: { a: "TypeScript" }, fileExtension: ".ts" },
+ { name: "CoffeeScript", fileExtension: ".coffee" }
+ ];
+
+ var languages2 = {
+ csharp: { name: "C#", paradigm: "object-oriented" },
+ fsharp: { name: "F#", paradigm: "functional" }
+ };
+
+ function Person(firstName, lastName, age)
+ {
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.age = age;
+ }
+
+ var family = {};
+ family.mother = new Person("Susan", "Doyle", 32);
+ family.father = new Person("John", "Doyle", 33);
+ family.daughter = new Person("Lily", "Doyle", 5);
+ family.son = new Person("Mike", "Doyle", 8);
+
+ var myMap = new Map();
+
+ myMap.set("a string", "value associated with 'a string'");
+ myMap.set(5, "value associated with 5");
+
+ var mySet = new Set();
+
+ mySet.add(1);
+ mySet.add(5);
+ mySet.add("some text");
+ mySet.add(null);
+ mySet.add(undefined);
+ </script>
+ </head>
+ <body>
+ <p>Hello world!</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-console.html b/browser/devtools/webconsole/test/test-console.html
new file mode 100644
index 000000000..27df226e4
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-console.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+ <meta charset="utf-8">
+ <title>Console test</title>
+ <script type="text/javascript">
+ function test() {
+ var str = "Dolske Digs Bacon, Now and Forevermore."
+ for (var i=0; i < 5; i++) {
+ console.log(str);
+ }
+ }
+ console.info("INLINE SCRIPT:");
+ test();
+ console.warn("I'm warning you, he will eat up all yr bacon.");
+ console.error("Error Message");
+ </script>
+ </head>
+ <body>
+ <h1 id="header">Heads Up Display Demo</h1>
+ <button onclick="test();">Log stuff about Dolske</button>
+ <div id="myDiv"></div>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-consoleiframes.html b/browser/devtools/webconsole/test/test-consoleiframes.html
new file mode 100644
index 000000000..a8176f93a
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-consoleiframes.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+ <script>
+ console.log("main file");
+ </script>
+</head>
+<body>
+<h1>iframe console test</h1>
+<iframe src="test-iframe1.html"></iframe>
+<iframe src="test-iframe2.html"></iframe>
+<iframe src="test-iframe3.html"></iframe>
+</body>
+</html> \ No newline at end of file
diff --git a/browser/devtools/webconsole/test/test-data.json b/browser/devtools/webconsole/test/test-data.json
new file mode 100644
index 000000000..471d240b5
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-data.json
@@ -0,0 +1 @@
+{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ] } \ No newline at end of file
diff --git a/browser/devtools/webconsole/test/test-data.json^headers^ b/browser/devtools/webconsole/test/test-data.json^headers^
new file mode 100644
index 000000000..7b5e82d4b
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-data.json^headers^
@@ -0,0 +1 @@
+Content-Type: application/json
diff --git a/browser/devtools/webconsole/test/test-duplicate-error.html b/browser/devtools/webconsole/test/test-duplicate-error.html
new file mode 100644
index 000000000..1b2691672
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-duplicate-error.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf-8">
+ <title>Console duplicate error test</title>
+ <!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+
+ See https://bugzilla.mozilla.org/show_bug.cgi?id=582201
+ -->
+ </head>
+ <body>
+ <h1>Heads Up Display - duplicate error test</h1>
+
+ <script type="text/javascript"><!--
+ fooDuplicateError1.bar();
+ // --></script>
+ </body>
+</html>
+
diff --git a/browser/devtools/webconsole/test/test-encoding-ISO-8859-1.html b/browser/devtools/webconsole/test/test-encoding-ISO-8859-1.html
new file mode 100644
index 000000000..cf19629f4
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-encoding-ISO-8859-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="ISO-8859-1">
+</head>
+<body>üöä</body>
+</html> \ No newline at end of file
diff --git a/browser/devtools/webconsole/test/test-error.html b/browser/devtools/webconsole/test/test-error.html
new file mode 100644
index 000000000..abf62a3f1
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-error.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf-8">
+ <title>Console error test</title>
+ </head>
+ <body>
+ <h1>Heads Up Display - error test</h1>
+ <p><button>generate error</button></p>
+
+ <script type="text/javascript"><!--
+ var button = document.getElementsByTagName("button")[0];
+
+ button.addEventListener("click", function clicker () {
+ button.removeEventListener("click", clicker, false);
+ fooBazBaz.bar();
+ }, false);
+ // --></script>
+ </body>
+</html>
+
diff --git a/browser/devtools/webconsole/test/test-eval-in-stackframe.html b/browser/devtools/webconsole/test/test-eval-in-stackframe.html
new file mode 100644
index 000000000..ec1bf3f30
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-eval-in-stackframe.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html dir="ltr" lang="en">
+ <head>
+ <meta charset="utf8">
+ <!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ <title>Test for bug 783499 - use the debugger API in the web console</title>
+ <script>
+ var foo = "globalFooBug783499";
+ var fooObj = {
+ testProp: "testValue",
+ };
+
+ function firstCall()
+ {
+ var foo = "fooFirstCall";
+ var foo3 = "foo3FirstCall";
+ secondCall();
+ }
+
+ function secondCall()
+ {
+ var foo2 = "foo2SecondCall";
+ var fooObj = {
+ testProp2: "testValue2",
+ };
+ var fooObj2 = {
+ testProp22: "testValue22",
+ };
+ debugger;
+ }
+ </script>
+ </head>
+ <body>
+ <p>Hello world!</p>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-file-location.js b/browser/devtools/webconsole/test/test-file-location.js
new file mode 100644
index 000000000..f97ce5725
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-file-location.js
@@ -0,0 +1,9 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+console.log("message for level log");
+console.info("message for level info");
+console.warn("message for level warn");
+console.error("message for level error");
+console.debug("message for level debug");
diff --git a/browser/devtools/webconsole/test/test-filter.html b/browser/devtools/webconsole/test/test-filter.html
new file mode 100644
index 000000000..219177bb2
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-filter.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+ <meta charset="utf-8">
+ <title>Console test</title>
+ <script type="text/javascript">
+ </script>
+ </head>
+ <body>
+ <h1>Heads Up Display Filter Test Page</h1>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-for-of.html b/browser/devtools/webconsole/test/test-for-of.html
new file mode 100644
index 000000000..876010c9e
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-for-of.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<body>
+<h1>a</h1>
+<div><p>b</p></div>
+<h2>c</h2>
+<p>d</p>
diff --git a/browser/devtools/webconsole/test/test-iframe-762593-insecure-form-action.html b/browser/devtools/webconsole/test/test-iframe-762593-insecure-form-action.html
new file mode 100644
index 000000000..d14b5cdd7
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-iframe-762593-insecure-form-action.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+ <head>
+ <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <h1>iframe 2</h1>
+ <p>This frame contains a password field inside a form with insecure action.</p>
+ <form action="http://test">
+ <input type="password" name="pwd">
+ </form>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-iframe-762593-insecure-frame.html b/browser/devtools/webconsole/test/test-iframe-762593-insecure-frame.html
new file mode 100644
index 000000000..505676acb
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-iframe-762593-insecure-frame.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+ <head>
+ <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <h1>iframe 1</h1>
+ <p>This frame is served with an insecure password field.</p>
+ <iframe src=
+ "http://example.com/browser/browser/devtools/webconsole/test/test-iframe-762593-insecure-form-action.html">
+ </iframe>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-iframe1.html b/browser/devtools/webconsole/test/test-iframe1.html
new file mode 100644
index 000000000..4dd4eddfe
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-iframe1.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+ <script>
+ console.log("iframe 1");
+ </script>
+</head>
+<body>
+<h1>iframe 1</h1>
+</body>
+</html> \ No newline at end of file
diff --git a/browser/devtools/webconsole/test/test-iframe2.html b/browser/devtools/webconsole/test/test-iframe2.html
new file mode 100644
index 000000000..c15884795
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-iframe2.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+ <script>
+ console.log("iframe 2");
+ blah;
+ </script>
+</head>
+<body>
+<h1>iframe 2</h1>
+</body>
+</html> \ No newline at end of file
diff --git a/browser/devtools/webconsole/test/test-iframe3.html b/browser/devtools/webconsole/test/test-iframe3.html
new file mode 100644
index 000000000..f0df8b669
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-iframe3.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+ <script>
+ console.log("iframe 3");
+ </script>
+</head>
+<body>
+<h1>iframe 3</h1>
+<iframe src="test-iframe1.html"></iframe>
+</body>
+</html> \ No newline at end of file
diff --git a/browser/devtools/webconsole/test/test-image.png b/browser/devtools/webconsole/test/test-image.png
new file mode 100644
index 000000000..769c63634
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-image.png
Binary files differ
diff --git a/browser/devtools/webconsole/test/test-mixedcontent-securityerrors.html b/browser/devtools/webconsole/test/test-mixedcontent-securityerrors.html
new file mode 100644
index 000000000..cb8cfdaaf
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-mixedcontent-securityerrors.html
@@ -0,0 +1,21 @@
+<!--
+ Bug 875456 - Log mixed content messages from the Mixed Content Blocker to the
+ Security Pane in the Web Console
+-->
+
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ <title>Mixed Content test - http on https</title>
+ <script src="testscript.js"></script>
+ <!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ </head>
+ <body>
+ <iframe src="http://example.com"></iframe>
+ <img src="http://example.com/tests/image/test/mochitest/blue.png"></img>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-mutation.html b/browser/devtools/webconsole/test/test-mutation.html
new file mode 100644
index 000000000..e80933b06
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-mutation.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf-8">
+ <title>Console mutation test</title>
+ <script>
+ window.onload = function (){
+ var node = document.createElement("div");
+ document.body.appendChild(node);
+ };
+ </script>
+ </head>
+ <body>
+ <h1>Heads Up Display DOM Mutation Test Page</h1>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-network-request.html b/browser/devtools/webconsole/test/test-network-request.html
new file mode 100644
index 000000000..f8f75d60b
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-network-request.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf-8">
+ <title>Console HTTP test page</title>
+ <script type="text/javascript"><!--
+ function makeXhr(aMethod, aUrl, aRequestBody, aCallback) {
+ var xmlhttp = new XMLHttpRequest();
+ xmlhttp.open(aMethod, aUrl, true);
+ xmlhttp.onreadystatechange = function() {
+ if (aCallback && xmlhttp.readyState == 4) {
+ aCallback();
+ }
+ };
+ xmlhttp.send(aRequestBody);
+ }
+
+ function testXhrGet(aCallback) {
+ makeXhr('get', 'test-data.json', null, aCallback);
+ }
+
+ function testXhrWarn(aCallback) {
+ makeXhr('get', 'http://example.com/browser/browser/devtools/netmonitor/test/sjs_cors-test-server.sjs', null, aCallback);
+ }
+
+ function testXhrPost(aCallback) {
+ makeXhr('post', 'test-data.json', "Hello world!", aCallback);
+ }
+ // --></script>
+ </head>
+ <body>
+ <h1>Heads Up Display HTTP Logging Testpage</h1>
+ <h2>This page is used to test the HTTP logging.</h2>
+
+ <form action="https://example.com/browser/browser/devtools/webconsole/test/test-network-request.html" method="post">
+ <input name="name" type="text" value="foo bar"><br>
+ <input name="age" type="text" value="144"><br>
+ </form>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-network.html b/browser/devtools/webconsole/test/test-network.html
new file mode 100644
index 000000000..69d3422e3
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-network.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+ <meta charset="utf-8">
+ <title>Console network test</title>
+ <script src="testscript.js?foo"></script>
+ </head>
+ <body>
+ <h1>Heads Up Display Network Test Page</h1>
+ <img src="test-image.png"></img>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-observe-http-ajax.html b/browser/devtools/webconsole/test/test-observe-http-ajax.html
new file mode 100644
index 000000000..5abcefdad
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-observe-http-ajax.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+ <meta charset="utf-8">
+ <title>Console HTTP test page</title>
+ <script type="text/javascript">
+ function test() {
+ var xmlhttp = new XMLHttpRequest();
+ xmlhttp.open('get', 'test-data.json', false);
+ xmlhttp.send(null);
+ }
+ </script>
+ </head>
+ <body onload="test();">
+ <h1>Heads Up Display HTTP & AJAX Test Page</h1>
+ <h2>This page fires an ajax request so we can see the http logging of the console</h2>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-own-console.html b/browser/devtools/webconsole/test/test-own-console.html
new file mode 100644
index 000000000..d1d18ebc2
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-own-console.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+<head>
+<meta charset="utf-8">
+<script>
+ var _console = {
+ foo: "bar"
+ }
+
+ window.console = _console;
+
+ function loadIFrame() {
+ var iframe = document.body.querySelector("iframe");
+ iframe.addEventListener("load", function() {
+ iframe.removeEventListener("load", arguments.callee, true);
+ }, true);
+
+ iframe.setAttribute("src", "test-console.html");
+ }
+</script>
+</head>
+<body>
+ <iframe></iframe>
+</body>
diff --git a/browser/devtools/webconsole/test/test-property-provider.html b/browser/devtools/webconsole/test/test-property-provider.html
new file mode 100644
index 000000000..532b00f44
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-property-provider.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
+ <meta charset="utf-8">
+ <title>Property provider test</title>
+ <script>
+ var testObj = {
+ testProp: 'testValue'
+ };
+ </script>
+ </head>
+ <body>
+ <h1>Heads Up Property Provider Test Page</h1>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-repeated-messages.html b/browser/devtools/webconsole/test/test-repeated-messages.html
new file mode 100644
index 000000000..97f5482e4
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-repeated-messages.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf8">
+ <title>Test for bugs 720180, 800510 and 865288</title>
+ <script>
+ function testConsole() {
+ // same line and column number
+ for(var i = 0; i < 2; i++) {
+ console.log("foo repeat");
+ }
+ console.log("foo repeat"); console.error("foo repeat");
+ }
+ function testConsoleObjects() {
+ for (var i = 0; i < 3; i++) {
+ var o = { id: "abba" + i };
+ console.log("abba", o);
+ }
+ }
+ </script>
+ <style>
+ body {
+ background-image: foobarz;
+ }
+ p {
+ background-image: foobarz;
+ }
+ </style>
+ <!--
+ - Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+ </head>
+ <body>
+ <p>Hello world!</p>
+ </body>
+</html>
+
diff --git a/browser/devtools/webconsole/test/test-result-format-as-string.html b/browser/devtools/webconsole/test/test-result-format-as-string.html
new file mode 100644
index 000000000..c3ab78ee7
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-result-format-as-string.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <title>Web Console test: jsterm eval format as a string</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ </head>
+ <body>
+ <p>Make sure js eval results are formatted as strings.</p>
+ <script>
+ document.querySelector("p").toSource = function() {
+ var element = document.createElement("div");
+ element.id = "foobar";
+ element.textContent = "bug772506_content";
+ element.setAttribute("onmousemove",
+ "(function () {" +
+ " gBrowser._bug772506 = 'foobar';" +
+ "})();"
+ );
+ return element;
+ };
+ </script>
+ </body>
+</html>
diff --git a/browser/devtools/webconsole/test/test-webconsole-error-observer.html b/browser/devtools/webconsole/test/test-webconsole-error-observer.html
new file mode 100644
index 000000000..8466bc6f2
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-webconsole-error-observer.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+ <head>
+ <meta charset="utf-8">
+ <title>WebConsoleErrorObserver test - bug 611032</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script type="text/javascript">
+ console.log("log Bazzle");
+ console.info("info Bazzle");
+ console.warn("warn Bazzle");
+ console.error("error Bazzle");
+
+ var foo = {};
+ foo.bazBug611032();
+ </script>
+ <style type="text/css">
+ .foo { color: cssColorBug611032; }
+ </style>
+ </head>
+ <body>
+ <h1>WebConsoleErrorObserver test</h1>
+ </body>
+</html>
+
diff --git a/browser/devtools/webconsole/test/test_bug1045902_console_csp_ignore_reflected_xss_message.html b/browser/devtools/webconsole/test/test_bug1045902_console_csp_ignore_reflected_xss_message.html
new file mode 100644
index 000000000..4872a1df7
--- /dev/null
+++ b/browser/devtools/webconsole/test/test_bug1045902_console_csp_ignore_reflected_xss_message.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Bug 1045902 - CSP: Log console message for 'reflected-xss'</title>
+</head>
+<body>
+Bug 1045902 - CSP: Log console message for 'reflected-xss'
+</body>
+</html>
diff --git a/browser/devtools/webconsole/test/test_bug1045902_console_csp_ignore_reflected_xss_message.html^headers^ b/browser/devtools/webconsole/test/test_bug1045902_console_csp_ignore_reflected_xss_message.html^headers^
new file mode 100644
index 000000000..0b234f0e8
--- /dev/null
+++ b/browser/devtools/webconsole/test/test_bug1045902_console_csp_ignore_reflected_xss_message.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: reflected-xss filter;
diff --git a/browser/devtools/webconsole/test/test_bug1092055_shouldwarn.html b/browser/devtools/webconsole/test/test_bug1092055_shouldwarn.html
new file mode 100644
index 000000000..ebb7773cb
--- /dev/null
+++ b/browser/devtools/webconsole/test/test_bug1092055_shouldwarn.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Bug 1092055 - Log console messages for non-top-level security errors</title>
+ <script src="test_bug1092055_shouldwarn.js"></script>
+ <!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+ -->
+</head>
+<body>
+Bug 1092055 - Log console messages for non-top-level security errors
+</body>
+</html>
diff --git a/browser/devtools/webconsole/test/test_bug1092055_shouldwarn.js b/browser/devtools/webconsole/test/test_bug1092055_shouldwarn.js
new file mode 100644
index 000000000..c7d5cec14
--- /dev/null
+++ b/browser/devtools/webconsole/test/test_bug1092055_shouldwarn.js
@@ -0,0 +1,2 @@
+// It doesn't matter what this script does, but the broken HSTS header sent
+// with it should result in warnings in the webconsole
diff --git a/browser/devtools/webconsole/test/test_bug1092055_shouldwarn.js^headers^ b/browser/devtools/webconsole/test/test_bug1092055_shouldwarn.js^headers^
new file mode 100644
index 000000000..f99377fc6
--- /dev/null
+++ b/browser/devtools/webconsole/test/test_bug1092055_shouldwarn.js^headers^
@@ -0,0 +1 @@
+Strict-Transport-Security: some complete nonsense
diff --git a/browser/devtools/webconsole/test/test_bug_1010953_cspro.html b/browser/devtools/webconsole/test/test_bug_1010953_cspro.html
new file mode 100644
index 000000000..83ac6391f
--- /dev/null
+++ b/browser/devtools/webconsole/test/test_bug_1010953_cspro.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 1010953 - Verify that CSP and CSPRO log different console
+messages.</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1010953">Mozilla Bug 1010953</a>
+
+
+<!-- this script file allowed by the CSP header (but not by the report-only header) -->
+<script src="http://some.example.com/test_bug_1010953_cspro.js"></script>
+
+<!-- this image allowed only be the CSP report-only header. -->
+<img src="http://some.example.com/test.png">
+</body>
+</html> \ No newline at end of file
diff --git a/browser/devtools/webconsole/test/test_bug_1010953_cspro.html^headers^ b/browser/devtools/webconsole/test/test_bug_1010953_cspro.html^headers^
new file mode 100644
index 000000000..03056e2cb
--- /dev/null
+++ b/browser/devtools/webconsole/test/test_bug_1010953_cspro.html^headers^
@@ -0,0 +1,2 @@
+Content-Security-Policy: default-src 'self'; img-src 'self'; script-src some.example.com;
+Content-Security-Policy-Report-Only: default-src 'self'; img-src some.example.com; script-src 'self'; report-uri https://example.com/ignored/; \ No newline at end of file
diff --git a/browser/devtools/webconsole/test/test_bug_770099_violation.html b/browser/devtools/webconsole/test/test_bug_770099_violation.html
new file mode 100644
index 000000000..ccbded87a
--- /dev/null
+++ b/browser/devtools/webconsole/test/test_bug_770099_violation.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <title>Test for Bug 770099 - policy violation</title>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=770099">Mozilla Bug 770099</a>
+<img src="http://some.example.com/test.png">
+</body>
+</html>
diff --git a/browser/devtools/webconsole/test/test_bug_770099_violation.html^headers^ b/browser/devtools/webconsole/test/test_bug_770099_violation.html^headers^
new file mode 100644
index 000000000..4c6fa3c26
--- /dev/null
+++ b/browser/devtools/webconsole/test/test_bug_770099_violation.html^headers^
@@ -0,0 +1 @@
+Content-Security-Policy: default-src 'self'
diff --git a/browser/devtools/webconsole/test/testscript.js b/browser/devtools/webconsole/test/testscript.js
new file mode 100644
index 000000000..c69919df4
--- /dev/null
+++ b/browser/devtools/webconsole/test/testscript.js
@@ -0,0 +1 @@
+console.log("running network console logging tests");
diff --git a/browser/devtools/webconsole/webconsole.js b/browser/devtools/webconsole/webconsole.js
index e9a5b277e..3c7d022b0 100644
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -1,4 +1,4 @@
-/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* 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
@@ -6,54 +6,45 @@
"use strict";
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Services",
- "resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
- "@mozilla.org/widget/clipboardhelper;1",
- "nsIClipboardHelper");
-
-XPCOMUtils.defineLazyModuleGetter(this, "GripClient",
- "resource://gre/modules/devtools/dbg-client.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "NetworkPanel",
- "resource:///modules/NetworkPanel.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "AutocompletePopup",
- "resource:///modules/devtools/AutocompletePopup.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
- "resource://gre/modules/devtools/WebConsoleUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "promise",
- "resource://gre/modules/commonjs/sdk/core/promise.js", "Promise");
-
-XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
- "resource:///modules/devtools/VariablesView.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "VariablesViewController",
- "resource:///modules/devtools/VariablesViewController.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
- "resource:///modules/devtools/shared/event-emitter.js");
-
-XPCOMUtils.defineLazyModuleGetter(this, "devtools",
- "resource://gre/modules/devtools/Loader.jsm");
+const {Cc, Ci, Cu} = require("chrome");
+
+let WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
+
+loader.lazyServiceGetter(this, "clipboardHelper",
+ "@mozilla.org/widget/clipboardhelper;1",
+ "nsIClipboardHelper");
+loader.lazyImporter(this, "Services", "resource://gre/modules/Services.jsm");
+loader.lazyImporter(this, "promise", "resource://gre/modules/Promise.jsm", "Promise");
+loader.lazyGetter(this, "EventEmitter", () => require("devtools/toolkit/event-emitter"));
+loader.lazyGetter(this, "AutocompletePopup",
+ () => require("devtools/shared/autocomplete-popup").AutocompletePopup);
+loader.lazyGetter(this, "ToolSidebar",
+ () => require("devtools/framework/sidebar").ToolSidebar);
+loader.lazyGetter(this, "NetworkPanel",
+ () => require("devtools/webconsole/network-panel").NetworkPanel);
+loader.lazyGetter(this, "ConsoleOutput",
+ () => require("devtools/webconsole/console-output").ConsoleOutput);
+loader.lazyGetter(this, "Messages",
+ () => require("devtools/webconsole/console-output").Messages);
+loader.lazyImporter(this, "EnvironmentClient", "resource://gre/modules/devtools/dbg-client.jsm");
+loader.lazyImporter(this, "ObjectClient", "resource://gre/modules/devtools/dbg-client.jsm");
+loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
+loader.lazyImporter(this, "VariablesViewController", "resource:///modules/devtools/VariablesViewController.jsm");
+loader.lazyImporter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
+loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);
+const XHTML_NS = "http://www.w3.org/1999/xhtml";
+
+const MIXED_CONTENT_LEARN_MORE = "https://developer.mozilla.org/docs/Security/MixedContent";
+
+const INSECURE_PASSWORDS_LEARN_MORE = "https://developer.mozilla.org/docs/Security/InsecurePasswords";
-// The XUL namespace.
-const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const STRICT_TRANSPORT_SECURITY_LEARN_MORE = "https://developer.mozilla.org/docs/Security/HTTP_Strict_Transport_Security";
-const MIXED_CONTENT_LEARN_MORE = "https://developer.mozilla.org/en/Security/MixedContent";
+const WEAK_SIGNATURE_ALGORITHM_LEARN_MORE = "https://developer.mozilla.org/docs/Security/Weak_Signature_Algorithm";
const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
@@ -61,11 +52,7 @@ const VARIABLES_VIEW_URL = "chrome://browser/content/devtools/widgets/VariablesV
const CONSOLE_DIR_VIEW_HEIGHT = 0.6;
-const IGNORED_SOURCE_URLS = ["debugger eval code", "self-hosted"];
-
-// The amount of time in milliseconds that must pass between messages to
-// trigger the display of a new group.
-const NEW_GROUP_DELAY = 5000;
+const IGNORED_SOURCE_URLS = ["debugger eval code"];
// The amount of time in milliseconds that we wait before performing a live
// search.
@@ -118,31 +105,35 @@ const SEVERITY_CLASS_FRAGMENTS = [
// Most of these rather idiosyncratic names are historical and predate the
// division of message type into "category" and "severity".
const MESSAGE_PREFERENCE_KEYS = [
-// Error Warning Info Log
- [ "network", null, null, "networkinfo", ], // Network
- [ "csserror", "cssparser", null, null, ], // CSS
- [ "exception", "jswarn", null, "jslog", ], // JS
- [ "error", "warn", "info", "log", ], // Web Developer
- [ null, null, null, null, ], // Input
- [ null, null, null, null, ], // Output
- [ "secerror", "secwarn", null, null, ], // Security
+// Error Warning Info Log
+ [ "network", "netwarn", "netxhr", "networkinfo", ], // Network
+ [ "csserror", "cssparser", null, "csslog", ], // CSS
+ [ "exception", "jswarn", null, "jslog", ], // JS
+ [ "error", "warn", "info", "log", ], // Web Developer
+ [ null, null, null, null, ], // Input
+ [ null, null, null, null, ], // Output
+ [ "secerror", "secwarn", null, null, ], // Security
];
// A mapping from the console API log event levels to the Web Console
// severities.
const LEVELS = {
error: SEVERITY_ERROR,
+ exception: SEVERITY_ERROR,
+ assert: SEVERITY_ERROR,
warn: SEVERITY_WARNING,
info: SEVERITY_INFO,
log: SEVERITY_LOG,
trace: SEVERITY_LOG,
+ table: SEVERITY_LOG,
debug: SEVERITY_LOG,
dir: SEVERITY_LOG,
group: SEVERITY_LOG,
groupCollapsed: SEVERITY_LOG,
groupEnd: SEVERITY_LOG,
time: SEVERITY_LOG,
- timeEnd: SEVERITY_LOG
+ timeEnd: SEVERITY_LOG,
+ count: SEVERITY_LOG
};
// The lowest HTTP response code (inclusive) that is considered an error.
@@ -158,13 +149,18 @@ const HISTORY_FORWARD = 1;
const GROUP_INDENT = 12;
// The number of messages to display in a single display update. If we display
-// too many messages at once we slow the Firefox UI too much.
+// too many messages at once we slow down the Firefox UI too much.
const MESSAGES_IN_INTERVAL = DEFAULT_LOG_LIMIT;
// The delay between display updates - tells how often we should *try* to push
// new messages to screen. This value is optimistic, updates won't always
// happen. Keep this low so the Web Console output feels live.
-const OUTPUT_INTERVAL = 50; // milliseconds
+const OUTPUT_INTERVAL = 20; // milliseconds
+
+// The maximum amount of time that can be spent doing cleanup inside of the
+// flush output callback. If things don't get cleaned up in this time,
+// then it will start again the next time it is called.
+const MAX_CLEANUP_TIME = 10; // milliseconds
// When the output queue has more than MESSAGES_IN_INTERVAL items we throttle
// output updates to this number of milliseconds. So during a lot of output we
@@ -177,11 +173,9 @@ const FILTER_PREFS_PREFIX = "devtools.webconsole.filter.";
// The minimum font size.
const MIN_FONT_SIZE = 10;
-// The maximum length of strings to be displayed by the Web Console.
-const MAX_LONG_STRING_LENGTH = 200000;
-
const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout";
const PREF_PERSISTLOG = "devtools.webconsole.persistlog";
+const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";
/**
* A WebConsoleFrame instance is an interactive console initialized *per target*
@@ -191,6 +185,7 @@ const PREF_PERSISTLOG = "devtools.webconsole.persistlog";
* The WebConsoleFrame is responsible for the actual Web Console UI
* implementation.
*
+ * @constructor
* @param object aWebConsoleOwner
* The WebConsole owner object.
*/
@@ -198,26 +193,33 @@ function WebConsoleFrame(aWebConsoleOwner)
{
this.owner = aWebConsoleOwner;
this.hudId = this.owner.hudId;
+ this.window = this.owner.iframeWindow;
this._repeatNodes = {};
this._outputQueue = [];
+ this._itemDestroyQueue = [];
this._pruneCategoriesQueue = {};
this._networkRequests = {};
this.filterPrefs = {};
+ this.output = new ConsoleOutput(this);
+
this._toggleFilter = this._toggleFilter.bind(this);
+ this._onPanelSelected = this._onPanelSelected.bind(this);
this._flushMessageQueue = this._flushMessageQueue.bind(this);
+ this._onToolboxPrefChanged = this._onToolboxPrefChanged.bind(this);
this._outputTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this._outputTimerInitialized = false;
EventEmitter.decorate(this);
}
+exports.WebConsoleFrame = WebConsoleFrame;
WebConsoleFrame.prototype = {
/**
* The WebConsole instance that owns this frame.
- * @see HUDService.jsm::WebConsole
+ * @see hudservice.js::WebConsole
* @type object
*/
owner: null,
@@ -342,6 +344,12 @@ WebConsoleFrame.prototype = {
outputNode: null,
/**
+ * The ConsoleOutput instance that manages all output.
+ * @type object
+ */
+ output: null,
+
+ /**
* The input element that allows the user to filter messages by string.
* @type nsIDOMElement
*/
@@ -358,6 +366,11 @@ WebConsoleFrame.prototype = {
// Used in tests.
_saveRequestAndResponseBodies: false,
+ // Chevron width at the starting of Web Console's input box.
+ _chevronWidth: 0,
+ // Width of the monospace characters in Web Console's input box.
+ _inputCharWidth: 0,
+
/**
* Tells whether to save the bodies of network requests and responses.
* Disabled by default to save memory.
@@ -394,6 +407,11 @@ WebConsoleFrame.prototype = {
*/
setSaveRequestAndResponseBodies:
function WCF_setSaveRequestAndResponseBodies(aValue) {
+ if (!this.webConsoleClient) {
+ // Don't continue if the webconsole disconnected.
+ return promise.resolve(null);
+ }
+
let deferred = promise.defer();
let newValue = !!aValue;
let toSet = {
@@ -414,24 +432,18 @@ WebConsoleFrame.prototype = {
return deferred.promise;
},
- _persistLog: null,
-
/**
- * Getter for the persistent logging preference. This value is cached per
- * instance to avoid reading the pref too often.
+ * Getter for the persistent logging preference.
* @type boolean
*/
get persistLog() {
- if (this._persistLog === null) {
- this._persistLog = Services.prefs.getBoolPref(PREF_PERSISTLOG);
- }
- return this._persistLog;
+ return Services.prefs.getBoolPref(PREF_PERSISTLOG);
},
/**
* Initialize the WebConsoleFrame instance.
* @return object
- * A Promise object for the initialization.
+ * A promise object for the initialization.
*/
init: function WCF_init()
{
@@ -444,7 +456,7 @@ WebConsoleFrame.prototype = {
*
* @private
* @return object
- * A Promise object that is resolved/reject based on the connection
+ * A promise object that is resolved/reject based on the connection
* result.
*/
_initConnection: function WCF__initConnection()
@@ -461,7 +473,7 @@ WebConsoleFrame.prototype = {
}, (aReason) => { // on failure
let node = this.createMessageNode(CATEGORY_JS, SEVERITY_ERROR,
aReason.error + ": " + aReason.message);
- this.outputMessage(CATEGORY_JS, node);
+ this.outputMessage(CATEGORY_JS, node, [aReason]);
this._initDefer.reject(aReason);
}).then(() => {
let id = WebConsoleUtils.supportsString(this.hudId);
@@ -477,9 +489,6 @@ WebConsoleFrame.prototype = {
*/
_initUI: function WCF__initUI()
{
- // Remember that this script is loaded in the webconsole.xul context:
- // |window| is the iframe global.
- this.window = window;
this.document = this.window.document;
this.rootElement = this.document.documentElement;
@@ -489,17 +498,20 @@ WebConsoleFrame.prototype = {
this._commandController = new CommandController(this);
this.window.controllers.insertControllerAt(0, this._commandController);
+ this._contextMenuHandler = new ConsoleContextMenu(this);
+
let doc = this.document;
this.filterBox = doc.querySelector(".hud-filter-box");
- this.outputNode = doc.querySelector(".hud-output-node");
+ this.outputNode = doc.getElementById("output-container");
this.completeNode = doc.querySelector(".jsterm-complete-node");
this.inputNode = doc.querySelector(".jsterm-input-node");
this._setFilterTextBoxEvents();
this._initFilterButtons();
- let fontSize = Services.prefs.getIntPref("devtools.webconsole.fontSize");
+ let fontSize = this.owner._browserConsole ?
+ Services.prefs.getIntPref("devtools.webconsole.fontSize") : 0;
if (fontSize != 0) {
fontSize = Math.max(MIN_FONT_SIZE, fontSize);
@@ -509,6 +521,17 @@ WebConsoleFrame.prototype = {
this.inputNode.style.fontSize = fontSize + "px";
}
+ if (this.owner._browserConsole) {
+ for (let id of ["Enlarge", "Reduce", "Reset"]) {
+ this.document.getElementById("cmd_fullZoom" + id)
+ .removeAttribute("disabled");
+ }
+ }
+
+ // Update the character width and height needed for the popup offset
+ // calculations.
+ this._updateCharSize();
+
let updateSaveBodiesPrefUI = (aElement) => {
this.getSaveRequestAndResponseBodies().then(aValue => {
aElement.setAttribute("checked", aValue);
@@ -524,25 +547,29 @@ WebConsoleFrame.prototype = {
});
}
+ let saveBodiesDisabled = !this.getFilterState("networkinfo") &&
+ !this.getFilterState("netxhr") &&
+ !this.getFilterState("network");
+
let saveBodies = doc.getElementById("saveBodies");
- saveBodies.addEventListener("click", reverseSaveBodiesPref);
- saveBodies.disabled = !this.getFilterState("networkinfo") &&
- !this.getFilterState("network");
+ saveBodies.addEventListener("command", reverseSaveBodiesPref);
+ saveBodies.disabled = saveBodiesDisabled;
let saveBodiesContextMenu = doc.getElementById("saveBodiesContextMenu");
- saveBodiesContextMenu.addEventListener("click", reverseSaveBodiesPref);
- saveBodiesContextMenu.disabled = !this.getFilterState("networkinfo") &&
- !this.getFilterState("network");
+ saveBodiesContextMenu.addEventListener("command", reverseSaveBodiesPref);
+ saveBodiesContextMenu.disabled = saveBodiesDisabled;
saveBodies.parentNode.addEventListener("popupshowing", () => {
updateSaveBodiesPrefUI(saveBodies);
saveBodies.disabled = !this.getFilterState("networkinfo") &&
+ !this.getFilterState("netxhr") &&
!this.getFilterState("network");
});
saveBodiesContextMenu.parentNode.addEventListener("popupshowing", () => {
updateSaveBodiesPrefUI(saveBodiesContextMenu);
saveBodiesContextMenu.disabled = !this.getFilterState("networkinfo") &&
+ !this.getFilterState("netxhr") &&
!this.getFilterState("network");
});
@@ -554,6 +581,42 @@ WebConsoleFrame.prototype = {
this.jsterm = new JSTerm(this);
this.jsterm.init();
+
+ let toolbox = gDevTools.getToolbox(this.owner.target);
+ if (toolbox) {
+ toolbox.on("webconsole-selected", this._onPanelSelected);
+ }
+
+ /*
+ * Focus input line whenever the output area is clicked.
+ * Reusing _addMEssageLinkCallback since it correctly filters
+ * drag and select events.
+ */
+ this._addFocusCallback(this.outputNode, (evt) => {
+ if ((evt.target.nodeName.toLowerCase() != "a") &&
+ (evt.target.parentNode.nodeName.toLowerCase() != "a")) {
+ this.jsterm.inputNode.focus();
+ }
+ });
+
+ // Toggle the timestamp on preference change
+ gDevTools.on("pref-changed", this._onToolboxPrefChanged);
+ this._onToolboxPrefChanged("pref-changed", {
+ pref: PREF_MESSAGE_TIMESTAMP,
+ newValue: Services.prefs.getBoolPref(PREF_MESSAGE_TIMESTAMP),
+ });
+
+ // focus input node
+ this.jsterm.inputNode.focus();
+ },
+
+ /**
+ * Sets the focus to JavaScript input field when the web console tab is
+ * selected or when there is a split console present.
+ * @private
+ */
+ _onPanelSelected: function WCF__onPanelSelected(evt, id)
+ {
this.jsterm.inputNode.focus();
},
@@ -563,9 +626,9 @@ WebConsoleFrame.prototype = {
*/
_initDefaultFilterPrefs: function WCF__initDefaultFilterPrefs()
{
- let prefs = ["network", "networkinfo", "csserror", "cssparser", "exception",
- "jswarn", "jslog", "error", "info", "warn", "log", "secerror",
- "secwarn"];
+ let prefs = ["network", "networkinfo", "csserror", "cssparser", "csslog",
+ "exception", "jswarn", "jslog", "error", "info", "warn", "log",
+ "secerror", "secwarn", "netwarn", "netxhr"];
for (let pref of prefs) {
this.filterPrefs[pref] = Services.prefs
.getBoolPref(this._filterPrefsPrefix + pref);
@@ -573,6 +636,28 @@ WebConsoleFrame.prototype = {
},
/**
+ * Attach / detach reflow listeners depending on the checked status
+ * of the `CSS > Log` menuitem.
+ *
+ * @param function [aCallback=null]
+ * Optional function to invoke when the listener has been
+ * added/removed.
+ *
+ */
+ _updateReflowActivityListener:
+ function WCF__updateReflowActivityListener(aCallback)
+ {
+ if (this.webConsoleClient) {
+ let pref = this._filterPrefsPrefix + "csslog";
+ if (Services.prefs.getBoolPref(pref)) {
+ this.webConsoleClient.startListeners(["ReflowActivity"], aCallback);
+ } else {
+ this.webConsoleClient.stopListeners(["ReflowActivity"], aCallback);
+ }
+ }
+ },
+
+ /**
* Sets the events for the filter input field.
* @private
*/
@@ -609,6 +694,9 @@ WebConsoleFrame.prototype = {
let categories = this.document
.querySelectorAll(".webconsole-filter-button[category]");
Array.forEach(categories, function(aButton) {
+ aButton.addEventListener("contextmenu", (aEvent) => {
+ aButton.open = true;
+ }, false);
aButton.addEventListener("click", this._toggleFilter, false);
let someChecked = false;
@@ -623,6 +711,7 @@ WebConsoleFrame.prototype = {
}, this);
aButton.setAttribute("checked", someChecked);
+ aButton.setAttribute("aria-pressed", someChecked);
}, this);
if (!this.owner._browserConsole) {
@@ -632,6 +721,15 @@ WebConsoleFrame.prototype = {
let jslog = this.document.querySelector("menuitem[prefKey=jslog]");
jslog.hidden = true;
}
+
+ if (Services.appinfo.OS == "Darwin") {
+ let net = this.document.querySelector("toolbarbutton[category=net]");
+ let accesskey = net.getAttribute("accesskeyMacOSX");
+ net.setAttribute("accesskey", accesskey);
+
+ let logging = this.document.querySelector("toolbarbutton[category=logging]");
+ logging.removeAttribute("accesskey");
+ }
},
/**
@@ -678,6 +776,34 @@ WebConsoleFrame.prototype = {
this.outputNode.style.fontSize = "";
Services.prefs.clearUserPref("devtools.webconsole.fontSize");
}
+ this._updateCharSize();
+ },
+
+ /**
+ * Calculates the width and height of a single character of the input box.
+ * This will be used in opening the popup at the correct offset.
+ *
+ * @private
+ */
+ _updateCharSize: function WCF__updateCharSize()
+ {
+ let doc = this.document;
+ let tempLabel = doc.createElementNS(XHTML_NS, "span");
+ let style = tempLabel.style;
+ style.position = "fixed";
+ style.padding = "0";
+ style.margin = "0";
+ style.width = "auto";
+ style.color = "transparent";
+ WebConsoleUtils.copyTextStyles(this.inputNode, tempLabel);
+ tempLabel.textContent = "x";
+ doc.documentElement.appendChild(tempLabel);
+ this._inputCharWidth = tempLabel.offsetWidth;
+ tempLabel.parentNode.removeChild(tempLabel);
+ // Calculate the width of the chevron placed at the beginning of the input
+ // box. Remove 4 more pixels to accomodate the padding of the popup.
+ this._chevronWidth = +doc.defaultView.getComputedStyle(this.inputNode)
+ .paddingLeft.replace(/[^0-9.]/g, "") - 4;
},
/**
@@ -692,7 +818,9 @@ WebConsoleFrame.prototype = {
{
let target = aEvent.target;
let tagName = target.tagName;
- if (tagName != aEvent.currentTarget.tagName) {
+ // Prevent toggle if generated from a contextmenu event (right click)
+ let isRightClick = (aEvent.button === 2); // right click is button 2;
+ if (tagName != aEvent.currentTarget.tagName || isRightClick) {
return;
}
@@ -716,18 +844,38 @@ WebConsoleFrame.prototype = {
break;
}
+ // Toggle on the targeted filter button, and if the user alt clicked,
+ // toggle off all other filter buttons and their associated filters.
let state = target.getAttribute("checked") !== "true";
+ if (aEvent.getModifierState("Alt")) {
+ let buttons = this.document
+ .querySelectorAll(".webconsole-filter-button");
+ Array.forEach(buttons, (button) => {
+ if (button !== target) {
+ button.setAttribute("checked", false);
+ button.setAttribute("aria-pressed", false);
+ this._setMenuState(button, false);
+ }
+ });
+ state = true;
+ }
target.setAttribute("checked", state);
+ target.setAttribute("aria-pressed", state);
// This is a filter button with a drop-down, and the user clicked the
// main part of the button. Go through all the severities and toggle
// their associated filters.
- let menuItems = target.querySelectorAll("menuitem");
- for (let i = 0; i < menuItems.length; i++) {
- menuItems[i].setAttribute("checked", state);
- let prefKey = menuItems[i].getAttribute("prefKey");
- this.setFilterState(prefKey, state);
+ this._setMenuState(target, state);
+
+ // CSS reflow logging can decrease web page performance.
+ // Make sure the option is always unchecked when the CSS filter button is selected.
+ // See bug 971798.
+ if (target.getAttribute("category") == "css" && state) {
+ let csslogMenuItem = target.querySelector("menuitem[prefKey=csslog]");
+ csslogMenuItem.setAttribute("checked", false);
+ this.setFilterState("csslog", false);
}
+
break;
}
@@ -739,8 +887,9 @@ WebConsoleFrame.prototype = {
this.setFilterState(prefKey, state);
// Disable the log response and request body if network logging is off.
- if (prefKey == "networkinfo" || prefKey == "network") {
+ if (prefKey == "networkinfo" || prefKey == "netxhr" || prefKey == "network") {
let checkState = !this.getFilterState("networkinfo") &&
+ !this.getFilterState("netxhr") &&
!this.getFilterState("network");
this.document.getElementById("saveBodies").disabled = checkState;
this.document.getElementById("saveBodiesContextMenu").disabled = checkState;
@@ -761,12 +910,32 @@ WebConsoleFrame.prototype = {
}
let toolbarButton = menuPopup.parentNode;
toolbarButton.setAttribute("checked", someChecked);
+ toolbarButton.setAttribute("aria-pressed", someChecked);
break;
}
}
},
/**
+ * Set the menu attributes for a specific toggle button.
+ *
+ * @private
+ * @param XULElement aTarget
+ * Button with drop down items to be toggled.
+ * @param boolean aState
+ * True if the menu item is being toggled on, and false otherwise.
+ */
+ _setMenuState: function WCF__setMenuState(aTarget, aState)
+ {
+ let menuItems = aTarget.querySelectorAll("menuitem");
+ Array.forEach(menuItems, (item) => {
+ item.setAttribute("checked", aState);
+ let prefKey = item.getAttribute("prefKey");
+ this.setFilterState(prefKey, aState);
+ });
+ },
+
+ /**
* Set the filter state for a specific toggle button.
*
* @param string aToggleType
@@ -778,6 +947,7 @@ WebConsoleFrame.prototype = {
this.filterPrefs[aToggleType] = aState;
this.adjustVisibilityForMessageType(aToggleType, aState);
Services.prefs.setBoolPref(this._filterPrefsPrefix + aToggleType, aState);
+ this._updateReflowActivityListener();
},
/**
@@ -831,25 +1001,23 @@ WebConsoleFrame.prototype = {
let outputNode = this.outputNode;
let doc = this.document;
- // Look for message nodes ("hud-msg-node") with the given preference key
- // ("hud-msg-error", "hud-msg-cssparser", etc.) and add or remove the
- // "hud-filtered-by-type" class, which turns on or off the display.
+ // Look for message nodes (".message") with the given preference key
+ // (filter="error", filter="cssparser", etc.) and add or remove the
+ // "filtered-by-type" class, which turns on or off the display.
- let xpath = ".//*[contains(@class, 'hud-msg-node') and " +
- "contains(concat(@class, ' '), 'hud-" + aPrefKey + " ')]";
+ let xpath = ".//*[contains(@class, 'message') and " +
+ "@filter='" + aPrefKey + "']";
let result = doc.evaluate(xpath, outputNode, null,
Ci.nsIDOMXPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
for (let i = 0; i < result.snapshotLength; i++) {
let node = result.snapshotItem(i);
if (aState) {
- node.classList.remove("hud-filtered-by-type");
+ node.classList.remove("filtered-by-type");
}
else {
- node.classList.add("hud-filtered-by-type");
+ node.classList.add("filtered-by-type");
}
}
-
- this.regroupOutput();
},
/**
@@ -859,25 +1027,23 @@ WebConsoleFrame.prototype = {
adjustVisibilityOnSearchStringChange:
function WCF_adjustVisibilityOnSearchStringChange()
{
- let nodes = this.outputNode.getElementsByClassName("hud-msg-node");
+ let nodes = this.outputNode.getElementsByClassName("message");
let searchString = this.filterBox.value;
for (let i = 0, n = nodes.length; i < n; ++i) {
let node = nodes[i];
// hide nodes that match the strings
- let text = node.clipboardText;
+ let text = node.textContent;
// if the text matches the words in aSearchString...
if (this.stringMatchesFilters(text, searchString)) {
- node.classList.remove("hud-filtered-by-string");
+ node.classList.remove("filtered-by-string");
}
else {
- node.classList.add("hud-filtered-by-string");
+ node.classList.add("filtered-by-string");
}
}
-
- this.regroupOutput();
},
/**
@@ -897,7 +1063,7 @@ WebConsoleFrame.prototype = {
let prefKey = MESSAGE_PREFERENCE_KEYS[aNode.category][aNode.severity];
if (prefKey && !this.getFilterState(prefKey)) {
// The node is filtered by type.
- aNode.classList.add("hud-filtered-by-type");
+ aNode.classList.add("filtered-by-type");
isFiltered = true;
}
@@ -907,11 +1073,11 @@ WebConsoleFrame.prototype = {
// if string matches the filter text
if (!this.stringMatchesFilters(text, search)) {
- aNode.classList.add("hud-filtered-by-string");
+ aNode.classList.add("filtered-by-string");
isFiltered = true;
}
- if (isFiltered && aNode.classList.contains("webconsole-msg-inspector")) {
+ if (isFiltered && aNode.classList.contains("inlined-variables-view")) {
aNode.classList.add("hidden-message");
}
@@ -930,15 +1096,17 @@ WebConsoleFrame.prototype = {
mergeFilteredMessageNode:
function WCF_mergeFilteredMessageNode(aOriginal, aFiltered)
{
- // childNodes[3].firstChild is the node containing the number of repetitions
- // of a node.
- let repeatNode = aOriginal.childNodes[3].firstChild;
+ let repeatNode = aOriginal.getElementsByClassName("message-repeats")[0];
if (!repeatNode) {
return; // no repeat node, return early.
}
let occurrences = parseInt(repeatNode.getAttribute("value")) + 1;
repeatNode.setAttribute("value", occurrences);
+ repeatNode.textContent = occurrences;
+ let str = l10n.getStr("messageRepeats.tooltip2");
+ repeatNode.title = PluralForm.get(occurrences, str)
+ .replace("#1", occurrences);
},
/**
@@ -953,7 +1121,7 @@ WebConsoleFrame.prototype = {
*/
_filterRepeatedMessage: function WCF__filterRepeatedMessage(aNode)
{
- let repeatNode = aNode.getElementsByClassName("webconsole-msg-repeat")[0];
+ let repeatNode = aNode.getElementsByClassName("message-repeats")[0];
if (!repeatNode) {
return null;
}
@@ -961,25 +1129,23 @@ WebConsoleFrame.prototype = {
let uid = repeatNode._uid;
let dupeNode = null;
- if (aNode.classList.contains("webconsole-msg-cssparser") ||
- aNode.classList.contains("webconsole-msg-security")) {
+ if (aNode.category == CATEGORY_CSS ||
+ aNode.category == CATEGORY_SECURITY) {
dupeNode = this._repeatNodes[uid];
if (!dupeNode) {
this._repeatNodes[uid] = aNode;
}
}
- else if (!aNode.classList.contains("webconsole-msg-network") &&
- !aNode.classList.contains("webconsole-msg-inspector") &&
- (aNode.classList.contains("webconsole-msg-console") ||
- aNode.classList.contains("webconsole-msg-exception") ||
- aNode.classList.contains("webconsole-msg-error"))) {
+ else if ((aNode.category == CATEGORY_WEBDEV ||
+ aNode.category == CATEGORY_JS) &&
+ aNode.category != CATEGORY_NETWORK &&
+ !aNode.classList.contains("inlined-variables-view")) {
let lastMessage = this.outputNode.lastChild;
if (!lastMessage) {
return null;
}
- let lastRepeatNode = lastMessage
- .getElementsByClassName("webconsole-msg-repeat")[0];
+ let lastRepeatNode = lastMessage.getElementsByClassName("message-repeats")[0];
if (lastRepeatNode && lastRepeatNode._uid == uid) {
dupeNode = lastMessage;
}
@@ -1044,6 +1210,7 @@ WebConsoleFrame.prototype = {
let level = aMessage.level;
let args = aMessage.arguments;
let objectActors = new Set();
+ let node = null;
// Gather the actor IDs.
args.forEach((aValue) => {
@@ -1057,41 +1224,33 @@ WebConsoleFrame.prototype = {
case "info":
case "warn":
case "error":
- case "debug":
+ case "exception":
+ case "assert":
+ case "debug": {
+ let msg = new Messages.ConsoleGeneric(aMessage);
+ node = msg.init(this.output).render().element;
+ break;
+ }
+ case "table": {
+ let msg = new Messages.ConsoleTable(aMessage);
+ node = msg.init(this.output).render().element;
+ break;
+ }
+ case "trace": {
+ let msg = new Messages.ConsoleTrace(aMessage);
+ node = msg.init(this.output).render().element;
+ break;
+ }
case "dir": {
body = { arguments: args };
let clipboardArray = [];
args.forEach((aValue) => {
clipboardArray.push(VariablesView.getString(aValue));
- if (aValue && typeof aValue == "object" &&
- aValue.type == "longString") {
- clipboardArray.push(l10n.getStr("longStringEllipsis"));
- }
});
clipboardText = clipboardArray.join(" ");
break;
}
- case "trace": {
- let filename = WebConsoleUtils.abbreviateSourceURL(aMessage.filename);
- let functionName = aMessage.functionName ||
- l10n.getStr("stacktrace.anonymousFunction");
-
- body = l10n.getFormatStr("stacktrace.outputMessage",
- [filename, functionName, sourceLine]);
-
- clipboardText = "";
-
- aMessage.stacktrace.forEach(function(aFrame) {
- clipboardText += aFrame.filename + " :: " +
- aFrame.functionName + " :: " +
- aFrame.lineNumber + "\n";
- });
-
- clipboardText = clipboardText.trimRight();
- break;
- }
-
case "group":
case "groupCollapsed":
clipboardText = body = aMessage.groupName;
@@ -1129,6 +1288,20 @@ WebConsoleFrame.prototype = {
break;
}
+ case "count": {
+ let counter = aMessage.counter;
+ if (!counter) {
+ return null;
+ }
+ if (counter.error) {
+ Cu.reportError(l10n.getStr(counter.error));
+ return null;
+ }
+ let msg = new Messages.ConsoleGeneric(aMessage);
+ node = msg.init(this.output).render().element;
+ break;
+ }
+
default:
Cu.reportError("Unknown Console API log level: " + level);
return null;
@@ -1140,9 +1313,9 @@ WebConsoleFrame.prototype = {
case "group":
case "groupCollapsed":
case "groupEnd":
- case "trace":
case "time":
case "timeEnd":
+ case "count":
for (let actor of objectActors) {
this._releaseObject(actor);
}
@@ -1153,30 +1326,22 @@ WebConsoleFrame.prototype = {
return null; // no need to continue
}
- let node = this.createMessageNode(CATEGORY_WEBDEV, LEVELS[level], body,
- sourceURL, sourceLine, clipboardText,
- level, aMessage.timeStamp);
- if (aMessage.private) {
- node.setAttribute("private", true);
+ if (!node) {
+ node = this.createMessageNode(CATEGORY_WEBDEV, LEVELS[level], body,
+ sourceURL, sourceLine, clipboardText,
+ level, aMessage.timeStamp);
+ if (aMessage.private) {
+ node.setAttribute("private", true);
+ }
}
if (objectActors.size > 0) {
node._objectActors = objectActors;
- let repeatNode = node.querySelector(".webconsole-msg-repeat");
- repeatNode._uid += [...objectActors].join("-");
- }
-
- // Make the node bring up the variables view, to allow the user to inspect
- // the stack trace.
- if (level == "trace") {
- node._stacktrace = aMessage.stacktrace;
-
- this.makeOutputMessageLink(node, () =>
- this.jsterm.openVariablesView({
- rawObject: node._stacktrace,
- autofocus: true,
- }));
+ if (!node._messageObject) {
+ let repeatNode = node.getElementsByClassName("message-repeats")[0];
+ repeatNode._uid += [...objectActors].join("-");
+ }
}
return node;
@@ -1195,26 +1360,6 @@ WebConsoleFrame.prototype = {
},
/**
- * The click event handler for objects shown inline coming from the
- * window.console API.
- *
- * @private
- * @param nsIDOMNode aAnchor
- * The object inspector anchor element. This is the clickable element
- * in the console.log message we display.
- * @param object aObjectActor
- * The object actor grip.
- */
- _consoleLogClick: function WCF__consoleLogClick(aAnchor, aObjectActor)
- {
- this.jsterm.openVariablesView({
- label: aAnchor.textContent,
- objectActor: aObjectActor,
- autofocus: true,
- });
- },
-
- /**
* Reports an error in the page source, either JavaScript or CSS.
*
* @param nsIScriptError aScriptError
@@ -1226,9 +1371,19 @@ WebConsoleFrame.prototype = {
{
// Warnings and legacy strict errors become warnings; other types become
// errors.
- let severity = SEVERITY_ERROR;
+ let severity = 'error';
if (aScriptError.warning || aScriptError.strict) {
- severity = SEVERITY_WARNING;
+ severity = 'warning';
+ }
+
+ let category = 'js';
+ switch(aCategory) {
+ case CATEGORY_CSS:
+ category = 'css';
+ break;
+ case CATEGORY_SECURITY:
+ category = 'security';
+ break;
}
let objectActors = new Set();
@@ -1246,14 +1401,26 @@ WebConsoleFrame.prototype = {
errorMessage = errorMessage.initial;
}
- let node = this.createMessageNode(aCategory, severity,
- errorMessage,
- aScriptError.sourceName,
- aScriptError.lineNumber, null, null,
- aScriptError.timeStamp);
- if (aScriptError.private) {
- node.setAttribute("private", true);
- }
+ // Create a new message
+ let msg = new Messages.Simple(errorMessage, {
+ location: {
+ url: aScriptError.sourceName,
+ line: aScriptError.lineNumber,
+ column: aScriptError.columnNumber
+ },
+ category: category,
+ severity: severity,
+ timestamp: aScriptError.timeStamp,
+ private: aScriptError.private,
+ filterDuplicates: true
+ });
+
+ let node = msg.init(this.output).render().element;
+
+ // Select the body of the message node that is displayed in the console
+ let msgBody = node.getElementsByClassName("message-body")[0];
+ // Add the more info link node to messages that belong to certain categories
+ this.addMoreInfoLink(msgBody, aScriptError);
if (objectActors.size > 0) {
node._objectActors = objectActors;
@@ -1315,82 +1482,92 @@ WebConsoleFrame.prototype = {
/**
* Log network event.
*
- * @param object aActorId
- * The network event actor ID to log.
+ * @param object aActor
+ * The network event actor to log.
* @return nsIDOMElement|null
* The message element to display in the Web Console output.
*/
- logNetEvent: function WCF_logNetEvent(aActorId)
+ logNetEvent: function WCF_logNetEvent(aActor)
{
- let networkInfo = this._networkRequests[aActorId];
+ let actorId = aActor.actor;
+ let networkInfo = this._networkRequests[actorId];
if (!networkInfo) {
return null;
}
let request = networkInfo.request;
-
- let msgNode = this.document.createElementNS(XUL_NS, "hbox");
-
- let methodNode = this.document.createElementNS(XUL_NS, "label");
- methodNode.setAttribute("value", request.method);
- methodNode.classList.add("webconsole-msg-body-piece");
- msgNode.appendChild(methodNode);
-
- let linkNode = this.document.createElementNS(XUL_NS, "hbox");
- linkNode.flex = 1;
- linkNode.classList.add("webconsole-msg-body-piece");
- linkNode.classList.add("webconsole-msg-link");
- msgNode.appendChild(linkNode);
-
- let urlNode = this.document.createElementNS(XUL_NS, "label");
- urlNode.flex = 1;
- urlNode.setAttribute("crop", "center");
- urlNode.setAttribute("title", request.url);
- urlNode.setAttribute("tooltiptext", request.url);
- urlNode.setAttribute("value", request.url);
- urlNode.classList.add("hud-clickable");
- urlNode.classList.add("webconsole-msg-body-piece");
- urlNode.classList.add("webconsole-msg-url");
- linkNode.appendChild(urlNode);
-
+ let clipboardText = request.method + " " + request.url;
let severity = SEVERITY_LOG;
+ if (networkInfo.isXHR) {
+ clipboardText = request.method + " XHR " + request.url;
+ severity = SEVERITY_INFO;
+ }
let mixedRequest =
WebConsoleUtils.isMixedHTTPSRequest(request.url, this.contentLocation);
if (mixedRequest) {
- urlNode.classList.add("webconsole-mixed-content");
- this.makeMixedContentNode(linkNode);
- // If we define a SEVERITY_SECURITY in the future, switch this to
- // SEVERITY_SECURITY.
severity = SEVERITY_WARNING;
}
- let statusNode = this.document.createElementNS(XUL_NS, "label");
- statusNode.setAttribute("value", "");
- statusNode.classList.add("hud-clickable");
- statusNode.classList.add("webconsole-msg-body-piece");
- statusNode.classList.add("webconsole-msg-status");
- linkNode.appendChild(statusNode);
-
- let clipboardText = request.method + " " + request.url;
+ let methodNode = this.document.createElementNS(XHTML_NS, "span");
+ methodNode.className = "method";
+ methodNode.textContent = request.method + " ";
let messageNode = this.createMessageNode(CATEGORY_NETWORK, severity,
- msgNode, null, null, clipboardText);
+ methodNode, null, null,
+ clipboardText);
if (networkInfo.private) {
messageNode.setAttribute("private", true);
}
-
- messageNode._connectionId = aActorId;
+ messageNode._connectionId = actorId;
messageNode.url = request.url;
- this.makeOutputMessageLink(messageNode, function WCF_net_message_link() {
+ let body = methodNode.parentNode;
+ body.setAttribute("aria-haspopup", true);
+
+ if (networkInfo.isXHR) {
+ let xhrNode = this.document.createElementNS(XHTML_NS, "span");
+ xhrNode.className = "xhr";
+ xhrNode.textContent = l10n.getStr("webConsoleXhrIndicator");
+ body.appendChild(xhrNode);
+ body.appendChild(this.document.createTextNode(" "));
+ }
+
+ let displayUrl = request.url;
+ let pos = displayUrl.indexOf("?");
+ if (pos > -1) {
+ displayUrl = displayUrl.substr(0, pos);
+ }
+
+ let urlNode = this.document.createElementNS(XHTML_NS, "a");
+ urlNode.className = "url";
+ urlNode.setAttribute("title", request.url);
+ urlNode.href = request.url;
+ urlNode.textContent = displayUrl;
+ urlNode.draggable = false;
+ body.appendChild(urlNode);
+ body.appendChild(this.document.createTextNode(" "));
+
+ if (mixedRequest) {
+ messageNode.classList.add("mixed-content");
+ this.makeMixedContentNode(body);
+ }
+
+ let statusNode = this.document.createElementNS(XHTML_NS, "a");
+ statusNode.className = "status";
+ body.appendChild(statusNode);
+
+ let onClick = () => {
if (!messageNode._panelOpen) {
this.openNetworkPanel(messageNode, networkInfo);
}
- }.bind(this));
+ };
+
+ this._addMessageLinkCallback(urlNode, onClick);
+ this._addMessageLinkCallback(statusNode, onClick);
networkInfo.node = messageNode;
- this._updateNetMessage(aActorId);
+ this._updateNetMessage(actorId);
return messageNode;
},
@@ -1406,19 +1583,85 @@ WebConsoleFrame.prototype = {
let mixedContentWarning = "[" + l10n.getStr("webConsoleMixedContentWarning") + "]";
// Mixed content warning message links to a Learn More page
- let mixedContentWarningNode = this.document.createElement("label");
- mixedContentWarningNode.setAttribute("value", mixedContentWarning);
- mixedContentWarningNode.setAttribute("title", mixedContentWarning);
- mixedContentWarningNode.classList.add("hud-clickable");
- mixedContentWarningNode.classList.add("webconsole-mixed-content-link");
+ let mixedContentWarningNode = this.document.createElementNS(XHTML_NS, "a");
+ mixedContentWarningNode.title = MIXED_CONTENT_LEARN_MORE;
+ mixedContentWarningNode.href = MIXED_CONTENT_LEARN_MORE;
+ mixedContentWarningNode.className = "learn-more-link";
+ mixedContentWarningNode.textContent = mixedContentWarning;
+ mixedContentWarningNode.draggable = false;
aLinkNode.appendChild(mixedContentWarningNode);
- mixedContentWarningNode.addEventListener("click", function(aEvent) {
+ this._addMessageLinkCallback(mixedContentWarningNode, (aEvent) => {
+ aEvent.stopPropagation();
this.owner.openLink(MIXED_CONTENT_LEARN_MORE);
- aEvent.preventDefault();
+ });
+ },
+
+ /**
+ * Adds a more info link node to messages based on the nsIScriptError object
+ * that we need to report to the console
+ *
+ * @param aNode
+ * The node to which we will be adding the more info link node
+ * @param aScriptError
+ * The script error object that we are reporting to the console
+ */
+ addMoreInfoLink: function WCF_addMoreInfoLink(aNode, aScriptError)
+ {
+ let url;
+ switch (aScriptError.category) {
+ case "Insecure Password Field":
+ url = INSECURE_PASSWORDS_LEARN_MORE;
+ break;
+ case "Mixed Content Message":
+ case "Mixed Content Blocker":
+ url = MIXED_CONTENT_LEARN_MORE;
+ break;
+ case "Invalid HSTS Headers":
+ url = STRICT_TRANSPORT_SECURITY_LEARN_MORE;
+ break;
+ case "SHA-1 Signature":
+ url = WEAK_SIGNATURE_ALGORITHM_LEARN_MORE;
+ break;
+ default:
+ // Unknown category. Return without adding more info node.
+ return;
+ }
+
+ this.addLearnMoreWarningNode(aNode, url);
+ },
+
+ /*
+ * Appends a clickable warning node to the node passed
+ * as a parameter to the function. When a user clicks on the appended
+ * warning node, the browser navigates to the provided url.
+ *
+ * @param aNode
+ * The node to which we will be adding a clickable warning node.
+ * @param aURL
+ * The url which points to the page where the user can learn more
+ * about security issues associated with the specific message that's
+ * being logged.
+ */
+ addLearnMoreWarningNode:
+ function WCF_addLearnMoreWarningNode(aNode, aURL)
+ {
+ let moreInfoLabel = "[" + l10n.getStr("webConsoleMoreInfoLabel") + "]";
+
+ let warningNode = this.document.createElementNS(XHTML_NS, "a");
+ warningNode.title = aURL;
+ warningNode.href = aURL;
+ warningNode.draggable = false;
+ warningNode.textContent = moreInfoLabel;
+ warningNode.className = "learn-more-link";
+
+ this._addMessageLinkCallback(warningNode, (aEvent) => {
aEvent.stopPropagation();
- }.bind(this));
+ this.owner.openLink(aURL);
+ });
+
+ aNode.appendChild(warningNode);
},
/**
@@ -1431,21 +1674,19 @@ WebConsoleFrame.prototype = {
*/
logFileActivity: function WCF_logFileActivity(aFileURI)
{
- let urlNode = this.document.createElementNS(XUL_NS, "label");
- urlNode.flex = 1;
- urlNode.setAttribute("crop", "center");
+ let urlNode = this.document.createElementNS(XHTML_NS, "a");
urlNode.setAttribute("title", aFileURI);
- urlNode.setAttribute("tooltiptext", aFileURI);
- urlNode.setAttribute("value", aFileURI);
- urlNode.classList.add("hud-clickable");
- urlNode.classList.add("webconsole-msg-url");
+ urlNode.className = "url";
+ urlNode.textContent = aFileURI;
+ urlNode.draggable = false;
+ urlNode.href = aFileURI;
let outputNode = this.createMessageNode(CATEGORY_NETWORK, SEVERITY_LOG,
urlNode, null, null, aFileURI);
- this.makeOutputMessageLink(outputNode, function WCF__onFileClick() {
+ this._addMessageLinkCallback(urlNode, () => {
this.owner.viewSource(aFileURI);
- }.bind(this));
+ });
return outputNode;
},
@@ -1462,23 +1703,49 @@ WebConsoleFrame.prototype = {
},
/**
- * Inform user that the window.console API has been replaced by a script
- * in a content page.
+ * Handle the reflow activity messages coming from the remote Web Console.
+ *
+ * @param object aMessage
+ * An object holding information about a reflow batch.
*/
- logWarningAboutReplacedAPI: function WCF_logWarningAboutReplacedAPI()
+ logReflowActivity: function WCF_logReflowActivity(aMessage)
{
- let node = this.createMessageNode(CATEGORY_JS, SEVERITY_WARNING,
- l10n.getStr("ConsoleAPIDisabled"));
- this.outputMessage(CATEGORY_JS, node);
+ let {start, end, sourceURL, sourceLine} = aMessage;
+ let duration = Math.round((end - start) * 100) / 100;
+ let node = this.document.createElementNS(XHTML_NS, "span");
+ if (sourceURL) {
+ node.textContent = l10n.getFormatStr("reflow.messageWithLink", [duration]);
+ let a = this.document.createElementNS(XHTML_NS, "a");
+ a.href = "#";
+ a.draggable = "false";
+ let filename = WebConsoleUtils.abbreviateSourceURL(sourceURL);
+ let functionName = aMessage.functionName || l10n.getStr("stacktrace.anonymousFunction");
+ a.textContent = l10n.getFormatStr("reflow.messageLinkText",
+ [functionName, filename, sourceLine]);
+ this._addMessageLinkCallback(a, () => {
+ this.owner.viewSourceInDebugger(sourceURL, sourceLine);
+ });
+ node.appendChild(a);
+ } else {
+ node.textContent = l10n.getFormatStr("reflow.messageWithNoLink", [duration]);
+ }
+ return this.createMessageNode(CATEGORY_CSS, SEVERITY_LOG, node);
+ },
+
+
+ handleReflowActivity: function WCF_handleReflowActivity(aMessage)
+ {
+ this.outputMessage(CATEGORY_CSS, this.logReflowActivity, [aMessage]);
},
/**
- * Inform user that the string he tries to view is too long.
+ * Inform user that the window.console API has been replaced by a script
+ * in a content page.
*/
- logWarningAboutStringTooLong: function WCF_logWarningAboutStringTooLong()
+ logWarningAboutReplacedAPI: function WCF_logWarningAboutReplacedAPI()
{
let node = this.createMessageNode(CATEGORY_JS, SEVERITY_WARNING,
- l10n.getStr("longStringTooLong"));
+ l10n.getStr("ConsoleAPIDisabled"));
this.outputMessage(CATEGORY_JS, node);
},
@@ -1500,6 +1767,7 @@ WebConsoleFrame.prototype = {
url: aActor.url,
method: aActor.method,
},
+ isXHR: aActor.isXHR,
response: {},
timings: {},
updates: [], // track the list of network event updates
@@ -1507,7 +1775,7 @@ WebConsoleFrame.prototype = {
};
this._networkRequests[aActor.actor] = networkInfo;
- this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [aActor.actor]);
+ this.outputMessage(CATEGORY_NETWORK, this.logNetEvent, [aActor]);
},
/**
@@ -1557,8 +1825,12 @@ WebConsoleFrame.prototype = {
break;
}
- if (networkInfo.node) {
- this._updateNetMessage(aActorId);
+ if (networkInfo.node && this._updateNetMessage(aActorId)) {
+ this.emit("new-messages", new Set([{
+ update: true,
+ node: networkInfo.node,
+ response: aPacket,
+ }]));
}
// For unit tests we pass the HTTP activity object to the test callback,
@@ -1577,6 +1849,8 @@ WebConsoleFrame.prototype = {
* @private
* @param string aActorId
* The network event actor ID for which you want to update the message.
+ * @return boolean
+ * |true| if the message node was updated, or |false| otherwise.
*/
_updateNetMessage: function WCF__updateNetMessage(aActorId)
{
@@ -1590,7 +1864,9 @@ WebConsoleFrame.prototype = {
let hasEventTimings = updates.indexOf("eventTimings") > -1;
let hasResponseStart = updates.indexOf("responseStart") > -1;
let request = networkInfo.request;
+ let methodText = (networkInfo.isXHR)? request.method + ' XHR' : request.method;
let response = networkInfo.response;
+ let updated = false;
if (hasEventTimings || hasResponseStart) {
let status = [];
@@ -1603,22 +1879,25 @@ WebConsoleFrame.prototype = {
}
let statusText = "[" + status.join(" ") + "]";
- let linkNode = messageNode.querySelector(".webconsole-msg-link");
- let statusNode = linkNode.querySelector(".webconsole-msg-status");
- statusNode.setAttribute("value", statusText);
+ let statusNode = messageNode.getElementsByClassName("status")[0];
+ statusNode.textContent = statusText;
- messageNode.clipboardText = [request.method, request.url, statusText]
+ messageNode.clipboardText = [methodText, request.url, statusText]
.join(" ");
if (hasResponseStart && response.status >= MIN_HTTP_ERROR_CODE &&
response.status <= MAX_HTTP_ERROR_CODE) {
this.setMessageType(messageNode, CATEGORY_NETWORK, SEVERITY_ERROR);
}
+
+ updated = true;
}
if (messageNode._netPanel) {
messageNode._netPanel.update();
}
+
+ return updated;
},
/**
@@ -1637,7 +1916,7 @@ WebConsoleFrame.prototype = {
let actor = aHttpActivity.actor;
if (actor) {
- this.webConsoleClient.getRequestHeaders(actor, function(aResponse) {
+ this.webConsoleClient.getRequestHeaders(actor, (aResponse) => {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getRequestHeaders:" +
aResponse.error);
@@ -1647,10 +1926,10 @@ WebConsoleFrame.prototype = {
aHttpActivity.request.headers = aResponse.headers;
this.webConsoleClient.getRequestCookies(actor, onRequestCookies);
- }.bind(this));
+ });
}
- let onRequestCookies = function(aResponse) {
+ let onRequestCookies = (aResponse) => {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getRequestCookies:" +
aResponse.error);
@@ -1660,9 +1939,9 @@ WebConsoleFrame.prototype = {
aHttpActivity.request.cookies = aResponse.cookies;
this.webConsoleClient.getResponseHeaders(actor, onResponseHeaders);
- }.bind(this);
+ };
- let onResponseHeaders = function(aResponse) {
+ let onResponseHeaders = (aResponse) => {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getResponseHeaders:" +
aResponse.error);
@@ -1672,9 +1951,9 @@ WebConsoleFrame.prototype = {
aHttpActivity.response.headers = aResponse.headers;
this.webConsoleClient.getResponseCookies(actor, onResponseCookies);
- }.bind(this);
+ };
- let onResponseCookies = function(aResponse) {
+ let onResponseCookies = (aResponse) => {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getResponseCookies:" +
aResponse.error);
@@ -1684,9 +1963,9 @@ WebConsoleFrame.prototype = {
aHttpActivity.response.cookies = aResponse.cookies;
this.webConsoleClient.getRequestPostData(actor, onRequestPostData);
- }.bind(this);
+ };
- let onRequestPostData = function(aResponse) {
+ let onRequestPostData = (aResponse) => {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getRequestPostData:" +
aResponse.error);
@@ -1697,9 +1976,9 @@ WebConsoleFrame.prototype = {
aHttpActivity.discardRequestBody = aResponse.postDataDiscarded;
this.webConsoleClient.getResponseContent(actor, onResponseContent);
- }.bind(this);
+ };
- let onResponseContent = function(aResponse) {
+ let onResponseContent = (aResponse) => {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getResponseContent:" +
aResponse.error);
@@ -1710,9 +1989,9 @@ WebConsoleFrame.prototype = {
aHttpActivity.discardResponseBody = aResponse.contentDiscarded;
this.webConsoleClient.getEventTimings(actor, onEventTimings);
- }.bind(this);
+ };
- let onEventTimings = function(aResponse) {
+ let onEventTimings = (aResponse) => {
if (aResponse.error) {
Cu.reportError("WCF_openNetworkPanel getEventTimings:" +
aResponse.error);
@@ -1722,9 +2001,9 @@ WebConsoleFrame.prototype = {
aHttpActivity.timings = aResponse.timings;
openPanel();
- }.bind(this);
+ };
- let openPanel = function() {
+ let openPanel = () => {
aNode._netPanel = netPanel;
let panel = netPanel.panel;
@@ -1740,7 +2019,7 @@ WebConsoleFrame.prototype = {
});
aNode._panelOpen = true;
- }.bind(this);
+ };
let netPanel = new NetworkPanel(this.popupset, aHttpActivity, this);
netPanel.linkNode = aNode;
@@ -1769,6 +2048,35 @@ WebConsoleFrame.prototype = {
},
/**
+ * Handler for the tabNavigated notification.
+ *
+ * @param string aEvent
+ * Event name.
+ * @param object aPacket
+ * Notification packet received from the server.
+ */
+ handleTabNavigated: function WCF_handleTabNavigated(aEvent, aPacket)
+ {
+ if (aEvent == "will-navigate") {
+ if (this.persistLog) {
+ let marker = new Messages.NavigationMarker(aPacket, Date.now());
+ this.output.addMessage(marker);
+ }
+ else {
+ this.jsterm.clearOutput();
+ }
+ }
+
+ if (aPacket.url) {
+ this.onLocationChange(aPacket.url, aPacket.title);
+ }
+
+ if (aEvent == "navigate" && !aPacket.nativeConsoleAPI) {
+ this.logWarningAboutReplacedAPI();
+ }
+ },
+
+ /**
* Output a message node. This filters a node appropriately, then sends it to
* the output, regrouping and pruning output as necessary.
*
@@ -1784,7 +2092,9 @@ WebConsoleFrame.prototype = {
* object and the arguments will be |aArguments|.
* @param array [aArguments]
* If a method is given to output the message element then the method
- * will be invoked with the list of arguments given here.
+ * will be invoked with the list of arguments given here. The last
+ * object in this array should be the packet received from the
+ * back end.
*/
outputMessage: function WCF_outputMessage(aCategory, aMethodOrNode, aArguments)
{
@@ -1796,9 +2106,7 @@ WebConsoleFrame.prototype = {
this._outputQueue.push([aCategory, aMethodOrNode, aArguments]);
- if (!this._outputTimerInitialized) {
- this._initOutputTimer();
- }
+ this._initOutputTimer();
},
/**
@@ -1810,21 +2118,31 @@ WebConsoleFrame.prototype = {
*/
_flushMessageQueue: function WCF__flushMessageQueue()
{
+ this._outputTimerInitialized = false;
if (!this._outputTimer) {
return;
}
- let timeSinceFlush = Date.now() - this._lastOutputFlush;
- if (this._outputQueue.length > MESSAGES_IN_INTERVAL &&
- timeSinceFlush < THROTTLE_UPDATES) {
- this._initOutputTimer();
- return;
- }
+ let startTime = Date.now();
+ let timeSinceFlush = startTime - this._lastOutputFlush;
+ let shouldThrottle = this._outputQueue.length > MESSAGES_IN_INTERVAL &&
+ timeSinceFlush < THROTTLE_UPDATES;
// Determine how many messages we can display now.
let toDisplay = Math.min(this._outputQueue.length, MESSAGES_IN_INTERVAL);
- if (toDisplay < 1) {
- this._outputTimerInitialized = false;
+
+ // If there aren't any messages to display (because of throttling or an
+ // empty queue), then take care of some cleanup. Destroy items that were
+ // pruned from the outputQueue before being displayed.
+ if (shouldThrottle || toDisplay < 1) {
+ while (this._itemDestroyQueue.length) {
+ if ((Date.now() - startTime) > MAX_CLEANUP_TIME) {
+ break;
+ }
+ this._destroyItem(this._itemDestroyQueue.pop());
+ }
+
+ this._initOutputTimer();
return;
}
@@ -1836,30 +2154,29 @@ WebConsoleFrame.prototype = {
}
let batch = this._outputQueue.splice(0, toDisplay);
- if (!batch.length) {
- this._outputTimerInitialized = false;
- return;
- }
-
let outputNode = this.outputNode;
let lastVisibleNode = null;
- let scrolledToBottom = Utils.isOutputScrolledToBottom(outputNode);
- let scrollBox = outputNode.scrollBoxObject.element;
-
+ let scrollNode = outputNode.parentNode;
let hudIdSupportsString = WebConsoleUtils.supportsString(this.hudId);
+ // We won't bother to try to restore scroll position if this is showing
+ // a lot of messages at once (and there are still items in the queue).
+ // It is going to purge whatever you were looking at anyway.
+ let scrolledToBottom = shouldPrune ||
+ Utils.isOutputScrolledToBottom(outputNode);
+
// Output the current batch of messages.
- let newMessages = new Set();
- let updatedMessages = new Set();
- for (let item of batch) {
+ let messages = new Set();
+ for (let i = 0; i < batch.length; i++) {
+ let item = batch[i];
let result = this._outputMessageFromQueue(hudIdSupportsString, item);
if (result) {
- if (result.isRepeated) {
- updatedMessages.add(result.isRepeated);
- }
- else {
- newMessages.add(result.node);
- }
+ messages.add({
+ node: result.isRepeated ? result.isRepeated : result.node,
+ response: result.message,
+ update: !!result.isRepeated,
+ });
+
if (result.visible && result.node == this.outputNode.lastChild) {
lastVisibleNode = result.node;
}
@@ -1867,12 +2184,15 @@ WebConsoleFrame.prototype = {
}
let oldScrollHeight = 0;
-
- // Prune messages if needed. We do not do this for every flush call to
- // improve performance.
let removedNodes = 0;
+
+ // Prune messages from the DOM, but only if needed.
if (shouldPrune || !this._outputQueue.length) {
- oldScrollHeight = scrollBox.scrollHeight;
+ // Only bother measuring the scrollHeight if not scrolled to bottom,
+ // since the oldScrollHeight will not be used if it is.
+ if (!scrolledToBottom) {
+ oldScrollHeight = scrollNode.scrollHeight;
+ }
let categories = Object.keys(this._pruneCategoriesQueue);
categories.forEach(function _pruneOutput(aCategory) {
@@ -1881,14 +2201,9 @@ WebConsoleFrame.prototype = {
this._pruneCategoriesQueue = {};
}
- // Regroup messages at the end of the queue.
- if (!this._outputQueue.length) {
- this.regroupOutput();
- }
-
let isInputOutput = lastVisibleNode &&
- (lastVisibleNode.classList.contains("webconsole-msg-input") ||
- lastVisibleNode.classList.contains("webconsole-msg-output"));
+ (lastVisibleNode.category == CATEGORY_INPUT ||
+ lastVisibleNode.category == CATEGORY_OUTPUT);
// Scroll to the new node if it is not filtered, and if the output node is
// scrolled at the bottom or if the new node is a jsterm input/output
@@ -1897,28 +2212,25 @@ WebConsoleFrame.prototype = {
Utils.scrollToVisible(lastVisibleNode);
}
else if (!scrolledToBottom && removedNodes > 0 &&
- oldScrollHeight != scrollBox.scrollHeight) {
+ oldScrollHeight != scrollNode.scrollHeight) {
// If there were pruned messages and if scroll is not at the bottom, then
// we need to adjust the scroll location.
- scrollBox.scrollTop -= oldScrollHeight - scrollBox.scrollHeight;
+ scrollNode.scrollTop -= oldScrollHeight - scrollNode.scrollHeight;
}
- if (newMessages.size) {
- this.emit("messages-added", newMessages);
- }
- if (updatedMessages.size) {
- this.emit("messages-updated", updatedMessages);
+ if (messages.size) {
+ this.emit("new-messages", messages);
}
- // If the queue is not empty, schedule another flush.
- if (this._outputQueue.length > 0) {
- this._initOutputTimer();
- }
- else {
- this._outputTimerInitialized = false;
- this._flushCallback && this._flushCallback();
+ // If the output queue is empty, then run _flushCallback.
+ if (this._outputQueue.length === 0 && this._flushCallback) {
+ if (this._flushCallback() === false) {
+ this._flushCallback = null;
+ }
}
+ this._initOutputTimer();
+
this._lastOutputFlush = Date.now();
},
@@ -1928,7 +2240,13 @@ WebConsoleFrame.prototype = {
*/
_initOutputTimer: function WCF__initOutputTimer()
{
- if (!this._outputTimer) {
+ let panelIsDestroyed = !this._outputTimer;
+ let alreadyScheduled = this._outputTimerInitialized;
+ let nothingToDo = !this._itemDestroyQueue.length &&
+ !this._outputQueue.length;
+
+ // Don't schedule a callback in the following cases:
+ if (panelIsDestroyed || alreadyScheduled || nothingToDo) {
return;
}
@@ -1958,6 +2276,10 @@ WebConsoleFrame.prototype = {
{
let [category, methodOrNode, args] = aItem;
+ // The last object in the args array should be message
+ // object or response packet received from the server.
+ let message = (args && args.length) ? args[args.length-1] : null;
+
let node = typeof methodOrNode == "function" ?
methodOrNode.apply(this, args || []) :
methodOrNode;
@@ -1995,6 +2317,7 @@ WebConsoleFrame.prototype = {
visible: visible,
node: node,
isRepeated: isRepeated,
+ message: message
};
},
@@ -2026,7 +2349,7 @@ WebConsoleFrame.prototype = {
let n = Math.max(0, indexes.length - limit);
pruned += n;
for (let i = n - 1; i >= 0; i--) {
- this._pruneItemFromQueue(this._outputQueue[indexes[i]]);
+ this._itemDestroyQueue.push(this._outputQueue[indexes[i]]);
this._outputQueue.splice(indexes[i], 1);
}
}
@@ -2036,14 +2359,18 @@ WebConsoleFrame.prototype = {
},
/**
- * Prune an item from the output queue.
+ * Destroy an item that was once in the outputQueue but isn't needed
+ * after all.
*
* @private
* @param array aItem
- * The item you want to remove from the output queue.
+ * The item you want to destroy. Does not remove it from the output
+ * queue.
*/
- _pruneItemFromQueue: function WCF__pruneItemFromQueue(aItem)
+ _destroyItem: function WCF__destroyItem(aItem)
{
+ // TODO: handle object releasing in a more elegant way once all console
+ // messages use the new API - bug 778766.
let [category, methodOrNode, args] = aItem;
if (typeof methodOrNode != "function" && methodOrNode._objectActors) {
for (let actor of methodOrNode._objectActors) {
@@ -2052,10 +2379,23 @@ WebConsoleFrame.prototype = {
methodOrNode._objectActors.clear();
}
+ if (methodOrNode == this.output._flushMessageQueue &&
+ args[0]._objectActors) {
+ for (let arg of args) {
+ if (!arg._objectActors) {
+ continue;
+ }
+ for (let actor of arg._objectActors) {
+ this._releaseObject(actor);
+ }
+ arg._objectActors.clear();
+ }
+ }
+
if (category == CATEGORY_NETWORK) {
let connectionId = null;
if (methodOrNode == this.logNetEvent) {
- connectionId = args[0];
+ connectionId = args[0].actor;
}
else if (typeof methodOrNode != "function") {
connectionId = methodOrNode._connectionId;
@@ -2102,15 +2442,11 @@ WebConsoleFrame.prototype = {
*/
pruneOutputIfNecessary: function WCF_pruneOutputIfNecessary(aCategory)
{
- let outputNode = this.outputNode;
let logLimit = Utils.logLimitForCategory(aCategory);
-
- let messageNodes = outputNode.getElementsByClassName("webconsole-msg-" +
- CATEGORY_CLASS_FRAGMENTS[aCategory]);
+ let messageNodes = this.outputNode.querySelectorAll(".message[category=" +
+ CATEGORY_CLASS_FRAGMENTS[aCategory] + "]");
let n = Math.max(0, messageNodes.length - logLimit);
- let toRemove = Array.prototype.slice.call(messageNodes, 0, n);
- toRemove.forEach(this.removeOutputMessage, this);
-
+ [...messageNodes].slice(0, n).forEach(this.removeOutputMessage, this);
return n;
},
@@ -2122,6 +2458,10 @@ WebConsoleFrame.prototype = {
*/
removeOutputMessage: function WCF_removeOutputMessage(aNode)
{
+ if (aNode._messageObject) {
+ aNode._messageObject.destroy();
+ }
+
if (aNode._objectActors) {
for (let actor of aNode._objectActors) {
this._releaseObject(actor);
@@ -2129,19 +2469,19 @@ WebConsoleFrame.prototype = {
aNode._objectActors.clear();
}
- if (aNode.classList.contains("webconsole-msg-cssparser") ||
- aNode.classList.contains("webconsole-msg-security")) {
- let repeatNode = aNode.getElementsByClassName("webconsole-msg-repeat")[0];
+ if (aNode.category == CATEGORY_CSS ||
+ aNode.category == CATEGORY_SECURITY) {
+ let repeatNode = aNode.getElementsByClassName("message-repeats")[0];
if (repeatNode && repeatNode._uid) {
delete this._repeatNodes[repeatNode._uid];
}
}
else if (aNode._connectionId &&
- aNode.classList.contains("webconsole-msg-network")) {
+ aNode.category == CATEGORY_NETWORK) {
delete this._networkRequests[aNode._connectionId];
this._releaseObject(aNode._connectionId);
}
- else if (aNode.classList.contains("webconsole-msg-inspector")) {
+ else if (aNode.classList.contains("inlined-variables-view")) {
let view = aNode._variablesView;
if (view) {
view.controller.releaseActors();
@@ -2149,32 +2489,7 @@ WebConsoleFrame.prototype = {
aNode._variablesView = null;
}
- if (aNode.parentNode) {
- aNode.parentNode.removeChild(aNode);
- }
- },
-
- /**
- * Splits the given console messages into groups based on their timestamps.
- */
- regroupOutput: function WCF_regroupOutput()
- {
- // Go through the nodes and adjust the placement of "webconsole-new-group"
- // classes.
- let nodes = this.outputNode.querySelectorAll(".hud-msg-node" +
- ":not(.hud-filtered-by-string):not(.hud-filtered-by-type)");
- let lastTimestamp;
- for (let i = 0, n = nodes.length; i < n; i++) {
- let thisTimestamp = nodes[i].timestamp;
- if (lastTimestamp != null &&
- thisTimestamp >= lastTimestamp + NEW_GROUP_DELAY) {
- nodes[i].classList.add("webconsole-new-group");
- }
- else {
- nodes[i].classList.remove("webconsole-new-group");
- }
- lastTimestamp = thisTimestamp;
- }
+ aNode.remove();
},
/**
@@ -2202,8 +2517,8 @@ WebConsoleFrame.prototype = {
* The timestamp to use for this message node. If omitted, the current
* date and time is used.
* @return nsIDOMNode
- * The message node: a XUL richlistitem ready to be inserted into
- * the Web Console output node.
+ * The message node: a DIV ready to be inserted into the Web Console
+ * output node.
*/
createMessageNode:
function WCF_createMessageNode(aCategory, aSeverity, aBody, aSourceURL,
@@ -2213,29 +2528,22 @@ WebConsoleFrame.prototype = {
aClipboardText = aBody.innerText;
}
+ let indentNode = this.document.createElementNS(XHTML_NS, "span");
+ indentNode.className = "indent";
+
+ // Apply the current group by indenting appropriately.
+ let indent = this.groupDepth * GROUP_INDENT;
+ indentNode.style.width = indent + "px";
+
// Make the icon container, which is a vertical box. Its purpose is to
// ensure that the icon stays anchored at the top of the message even for
// long multi-line messages.
- let iconContainer = this.document.createElementNS(XUL_NS, "vbox");
- iconContainer.classList.add("webconsole-msg-icon-container");
- // Apply the curent group by indenting appropriately.
- iconContainer.style.marginLeft = this.groupDepth * GROUP_INDENT + "px";
-
- // Make the icon node. It's sprited and the actual region of the image is
- // determined by CSS rules.
- let iconNode = this.document.createElementNS(XUL_NS, "image");
- iconNode.classList.add("webconsole-msg-icon");
- iconContainer.appendChild(iconNode);
-
- // Make the spacer that positions the icon.
- let spacer = this.document.createElementNS(XUL_NS, "spacer");
- spacer.flex = 1;
- iconContainer.appendChild(spacer);
+ let iconContainer = this.document.createElementNS(XHTML_NS, "span");
+ iconContainer.className = "icon";
// Create the message body, which contains the actual text of the message.
- let bodyNode = this.document.createElementNS(XUL_NS, "description");
- bodyNode.flex = 1;
- bodyNode.classList.add("webconsole-msg-body");
+ let bodyNode = this.document.createElementNS(XHTML_NS, "span");
+ bodyNode.className = "message-body-wrapper message-body devtools-monospace";
// Store the body text, since it is needed later for the variables view.
let body = aBody;
@@ -2245,8 +2553,15 @@ WebConsoleFrame.prototype = {
(aBody + (aSourceURL ? " @ " + aSourceURL : "") +
(aSourceLine ? ":" + aSourceLine : ""));
+ let timestamp = aTimeStamp || Date.now();
+
// Create the containing node and append all its elements to it.
- let node = this.document.createElementNS(XUL_NS, "richlistitem");
+ let node = this.document.createElementNS(XHTML_NS, "div");
+ node.id = "console-msg-" + gSequenceId();
+ node.className = "message";
+ node.clipboardText = aClipboardText;
+ node.timestamp = timestamp;
+ this.setMessageType(node, aCategory, aSeverity);
if (aBody instanceof Ci.nsIDOMNode) {
bodyNode.appendChild(aBody);
@@ -2256,10 +2571,6 @@ WebConsoleFrame.prototype = {
if (aLevel == "dir") {
str = VariablesView.getString(aBody.arguments[0]);
}
- else if (["log", "info", "warn", "error", "debug"].indexOf(aLevel) > -1 &&
- typeof aBody == "object") {
- this._makeConsoleLogMessageBody(node, bodyNode, aBody);
- }
else {
str = aBody;
}
@@ -2270,48 +2581,47 @@ WebConsoleFrame.prototype = {
}
}
- let repeatContainer = this.document.createElementNS(XUL_NS, "hbox");
- repeatContainer.setAttribute("align", "start");
- let repeatNode = this.document.createElementNS(XUL_NS, "label");
- repeatNode.setAttribute("value", "1");
- repeatNode.classList.add("webconsole-msg-repeat");
- repeatNode._uid = [bodyNode.textContent, aCategory, aSeverity, aLevel,
- aSourceURL, aSourceLine].join(":");
- repeatContainer.appendChild(repeatNode);
+ // Add the message repeats node only when needed.
+ let repeatNode = null;
+ if (aCategory != CATEGORY_INPUT &&
+ aCategory != CATEGORY_OUTPUT &&
+ aCategory != CATEGORY_NETWORK &&
+ !(aCategory == CATEGORY_CSS && aSeverity == SEVERITY_LOG)) {
+ repeatNode = this.document.createElementNS(XHTML_NS, "span");
+ repeatNode.setAttribute("value", "1");
+ repeatNode.className = "message-repeats";
+ repeatNode.textContent = 1;
+ repeatNode._uid = [bodyNode.textContent, aCategory, aSeverity, aLevel,
+ aSourceURL, aSourceLine].join(":");
+ }
// Create the timestamp.
- let timestampNode = this.document.createElementNS(XUL_NS, "label");
- timestampNode.classList.add("webconsole-timestamp");
- let timestamp = aTimeStamp || Date.now();
+ let timestampNode = this.document.createElementNS(XHTML_NS, "span");
+ timestampNode.className = "timestamp devtools-monospace";
+
let timestampString = l10n.timestampString(timestamp);
- timestampNode.setAttribute("value", timestampString);
+ timestampNode.textContent = timestampString + " ";
// Create the source location (e.g. www.example.com:6) that sits on the
// right side of the message, if applicable.
let locationNode;
if (aSourceURL && IGNORED_SOURCE_URLS.indexOf(aSourceURL) == -1) {
- locationNode = this.createLocationNode(aSourceURL, aSourceLine);
+ locationNode = this.createLocationNode({url: aSourceURL,
+ line: aSourceLine});
}
- node.clipboardText = aClipboardText;
- node.classList.add("hud-msg-node");
-
- node.timestamp = timestamp;
- this.setMessageType(node, aCategory, aSeverity);
-
node.appendChild(timestampNode);
+ node.appendChild(indentNode);
node.appendChild(iconContainer);
// Display the variables view after the message node.
if (aLevel == "dir") {
- let viewContainer = this.document.createElement("hbox");
- viewContainer.flex = 1;
- viewContainer.height = this.outputNode.clientHeight *
- CONSOLE_DIR_VIEW_HEIGHT;
+ bodyNode.style.height = (this.window.innerHeight *
+ CONSOLE_DIR_VIEW_HEIGHT) + "px";
let options = {
objectActor: body.arguments[0],
- targetElement: viewContainer,
+ targetElement: bodyNode,
hideFilterInput: true,
};
this.jsterm.openVariablesView(options).then((aView) => {
@@ -2321,282 +2631,125 @@ WebConsoleFrame.prototype = {
}
});
- let bodyContainer = this.document.createElement("vbox");
- bodyContainer.flex = 1;
- bodyContainer.appendChild(bodyNode);
- bodyContainer.appendChild(viewContainer);
- node.appendChild(bodyContainer);
- node.classList.add("webconsole-msg-inspector");
+ node.classList.add("inlined-variables-view");
}
- else {
- node.appendChild(bodyNode);
+
+ node.appendChild(bodyNode);
+ if (repeatNode) {
+ node.appendChild(repeatNode);
}
- node.appendChild(repeatContainer);
if (locationNode) {
node.appendChild(locationNode);
}
-
- node.setAttribute("id", "console-msg-" + gSequenceId());
+ node.appendChild(this.document.createTextNode("\n"));
return node;
},
/**
- * Make the message body for console.log() calls.
- *
- * @private
- * @param nsIDOMElement aMessage
- * The message element that holds the output for the given call.
- * @param nsIDOMElement aContainer
- * The specific element that will hold each part of the console.log
- * output.
- * @param object aBody
- * The object given by this.logConsoleAPIMessage(). This object holds
- * the call information that we need to display - mainly the arguments
- * array of the given API call.
- */
- _makeConsoleLogMessageBody:
- function WCF__makeConsoleLogMessageBody(aMessage, aContainer, aBody)
- {
- Object.defineProperty(aMessage, "_panelOpen", {
- get: function() {
- let nodes = aContainer.querySelectorAll(".hud-clickable");
- return Array.prototype.some.call(nodes, function(aNode) {
- return aNode._panelOpen;
- });
- },
- enumerable: true,
- configurable: false
- });
-
- aBody.arguments.forEach(function(aItem) {
- if (aContainer.firstChild) {
- aContainer.appendChild(this.document.createTextNode(" "));
- }
-
- let text = VariablesView.getString(aItem);
- let inspectable = !VariablesView.isPrimitive({ value: aItem });
-
- if (aItem && typeof aItem != "object" || !inspectable) {
- aContainer.appendChild(this.document.createTextNode(text));
-
- if (aItem.type && aItem.type == "longString") {
- let ellipsis = this.document.createElement("description");
- ellipsis.classList.add("hud-clickable");
- ellipsis.classList.add("longStringEllipsis");
- ellipsis.textContent = l10n.getStr("longStringEllipsis");
-
- let formatter = function(s) '"' + s + '"';
-
- this._addMessageLinkCallback(ellipsis,
- this._longStringClick.bind(this, aMessage, aItem, formatter));
-
- aContainer.appendChild(ellipsis);
- }
- return;
- }
-
- // For inspectable objects.
- let elem = this.document.createElement("description");
- elem.classList.add("hud-clickable");
- elem.setAttribute("aria-haspopup", "true");
- elem.appendChild(this.document.createTextNode(text));
-
- this._addMessageLinkCallback(elem,
- this._consoleLogClick.bind(this, elem, aItem));
-
- aContainer.appendChild(elem);
- }, this);
- },
-
- /**
- * Click event handler for the ellipsis shown immediately after a long string.
- * This method retrieves the full string and updates the console output to
- * show it.
- *
- * @private
- * @param nsIDOMElement aMessage
- * The message element.
- * @param object aActor
- * The LongStringActor instance we work with.
- * @param [function] aFormatter
- * Optional function you can use to format the string received from the
- * server, before being displayed in the console.
- * @param nsIDOMElement aEllipsis
- * The DOM element the user can click on to expand the string.
- * @param nsIDOMEvent aEvent
- * The DOM click event triggered by the user.
- */
- _longStringClick:
- function WCF__longStringClick(aMessage, aActor, aFormatter, aEllipsis, aEvent)
- {
- aEvent.preventDefault();
-
- if (!aFormatter) {
- aFormatter = function(s) s;
- }
-
- let longString = this.webConsoleClient.longString(aActor);
- let toIndex = Math.min(longString.length, MAX_LONG_STRING_LENGTH);
- longString.substring(longString.initial.length, toIndex,
- function WCF__onSubstring(aResponse) {
- if (aResponse.error) {
- Cu.reportError("WCF__longStringClick substring failure: " +
- aResponse.error);
- return;
- }
-
- let node = aEllipsis.previousSibling;
- node.textContent = aFormatter(longString.initial + aResponse.substring);
- aEllipsis.parentNode.removeChild(aEllipsis);
-
- if (aMessage.category == CATEGORY_WEBDEV ||
- aMessage.category == CATEGORY_OUTPUT) {
- aMessage.clipboardText = aMessage.textContent;
- }
-
- this.emit("messages-updated", new Set([aMessage]));
-
- if (toIndex != longString.length) {
- this.logWarningAboutStringTooLong();
- }
- }.bind(this));
- },
-
- /**
- * Creates the XUL label that displays the textual location of an incoming
+ * Creates the anchor that displays the textual location of an incoming
* message.
*
- * @param string aSourceURL
- * The URL of the source file responsible for the error.
- * @param number aSourceLine [optional]
- * The line number on which the error occurred. If zero or omitted,
- * there is no line number associated with this message.
+ * @param object aLocation
+ * An object containing url, line and column number of the message source (destructured).
+ * @param string aTarget [optional]
+ * Tells which tool to open the link with, on click. Supported tools:
+ * jsdebugger, styleeditor, scratchpad.
* @return nsIDOMNode
- * The new XUL label node, ready to be added to the message node.
+ * The new anchor element, ready to be added to the message node.
*/
- createLocationNode: function WCF_createLocationNode(aSourceURL, aSourceLine)
+ createLocationNode:
+ function WCF_createLocationNode({url, line, column}, aTarget)
{
- let locationNode = this.document.createElementNS(XUL_NS, "label");
+ if (!url) {
+ url = "";
+ }
+ let locationNode = this.document.createElementNS(XHTML_NS, "a");
+ let filenameNode = this.document.createElementNS(XHTML_NS, "span");
// Create the text, which consists of an abbreviated version of the URL
- // plus an optional line number. Scratchpad URLs should not be abbreviated.
- let displayLocation;
+ // Scratchpad URLs should not be abbreviated.
+ let filename;
let fullURL;
+ let isScratchpad = false;
- if (/^Scratchpad\/\d+$/.test(aSourceURL)) {
- displayLocation = aSourceURL;
- fullURL = aSourceURL;
+ if (/^Scratchpad\/\d+$/.test(url)) {
+ filename = url;
+ fullURL = url;
+ isScratchpad = true;
}
else {
- fullURL = aSourceURL.split(" -> ").pop();
- displayLocation = WebConsoleUtils.abbreviateSourceURL(fullURL);
+ fullURL = url.split(" -> ").pop();
+ filename = WebConsoleUtils.abbreviateSourceURL(fullURL);
}
- if (aSourceLine) {
- displayLocation += ":" + aSourceLine;
- locationNode.sourceLine = aSourceLine;
- }
-
- locationNode.setAttribute("value", displayLocation);
+ filenameNode.className = "filename";
+ filenameNode.textContent = " " + (filename || l10n.getStr("unknownLocation"));
+ locationNode.appendChild(filenameNode);
- // Style appropriately.
- locationNode.setAttribute("crop", "center");
- locationNode.setAttribute("title", aSourceURL);
- locationNode.setAttribute("tooltiptext", aSourceURL);
- locationNode.classList.add("webconsole-location");
- locationNode.classList.add("text-link");
+ locationNode.href = isScratchpad || !fullURL ? "#" : fullURL;
+ locationNode.draggable = false;
+ if (aTarget) {
+ locationNode.target = aTarget;
+ }
+ locationNode.setAttribute("title", url);
+ locationNode.className = "message-location theme-link devtools-monospace";
// Make the location clickable.
- locationNode.addEventListener("click", () => {
- if (/^Scratchpad\/\d+$/.test(aSourceURL)) {
- let wins = Services.wm.getEnumerator("devtools:scratchpad");
-
- while (wins.hasMoreElements()) {
- let win = wins.getNext();
-
- if (win.Scratchpad.uniqueName === aSourceURL) {
- win.focus();
- return;
- }
- }
+ let onClick = () => {
+ let target = locationNode.target;
+ if (target == "scratchpad" || isScratchpad) {
+ this.owner.viewSourceInScratchpad(url);
+ return;
}
- else if (locationNode.parentNode.category == CATEGORY_CSS) {
- this.owner.viewSourceInStyleEditor(fullURL, aSourceLine);
+
+ let category = locationNode.parentNode.category;
+ if (target == "styleeditor" || category == CATEGORY_CSS) {
+ this.owner.viewSourceInStyleEditor(fullURL, line);
}
- else if (locationNode.parentNode.category == CATEGORY_JS ||
- locationNode.parentNode.category == CATEGORY_WEBDEV) {
- this.owner.viewSourceInDebugger(fullURL, aSourceLine);
+ else if (target == "jsdebugger" ||
+ category == CATEGORY_JS || category == CATEGORY_WEBDEV) {
+ this.owner.viewSourceInDebugger(fullURL, line);
}
else {
- this.owner.viewSource(fullURL, aSourceLine);
+ this.owner.viewSource(fullURL, line);
}
- }, true);
+ };
+
+ if (fullURL) {
+ this._addMessageLinkCallback(locationNode, onClick);
+ }
+
+ if (line) {
+ let lineNumberNode = this.document.createElementNS(XHTML_NS, "span");
+ lineNumberNode.className = "line-number";
+ lineNumberNode.textContent = ":" + line + (column >= 0 ? ":" + column : "");
+ locationNode.appendChild(lineNumberNode);
+ locationNode.sourceLine = line;
+ }
return locationNode;
},
/**
- * Adjusts the category and severity of the given message, clearing the old
- * category and severity if present.
+ * Adjusts the category and severity of the given message.
*
* @param nsIDOMNode aMessageNode
* The message node to alter.
- * @param number aNewCategory
- * The new category for the message; one of the CATEGORY_ constants.
- * @param number aNewSeverity
- * The new severity for the message; one of the SEVERITY_ constants.
+ * @param number aCategory
+ * The category for the message; one of the CATEGORY_ constants.
+ * @param number aSeverity
+ * The severity for the message; one of the SEVERITY_ constants.
* @return void
*/
setMessageType:
- function WCF_setMessageType(aMessageNode, aNewCategory, aNewSeverity)
- {
- // Remove the old CSS classes, if applicable.
- if ("category" in aMessageNode) {
- let oldCategory = aMessageNode.category;
- let oldSeverity = aMessageNode.severity;
- aMessageNode.classList.remove("webconsole-msg-" +
- CATEGORY_CLASS_FRAGMENTS[oldCategory]);
- aMessageNode.classList.remove("webconsole-msg-" +
- SEVERITY_CLASS_FRAGMENTS[oldSeverity]);
- let key = "hud-" + MESSAGE_PREFERENCE_KEYS[oldCategory][oldSeverity];
- aMessageNode.classList.remove(key);
- }
-
- // Add in the new CSS classes.
- aMessageNode.category = aNewCategory;
- aMessageNode.severity = aNewSeverity;
- aMessageNode.classList.add("webconsole-msg-" +
- CATEGORY_CLASS_FRAGMENTS[aNewCategory]);
- aMessageNode.classList.add("webconsole-msg-" +
- SEVERITY_CLASS_FRAGMENTS[aNewSeverity]);
- let key = "hud-" + MESSAGE_PREFERENCE_KEYS[aNewCategory][aNewSeverity];
- aMessageNode.classList.add(key);
- },
-
- /**
- * Make a link given an output element.
- *
- * @param nsIDOMNode aNode
- * The message element you want to make a link for.
- * @param function aCallback
- * The function you want invoked when the user clicks on the message
- * element.
- */
- makeOutputMessageLink: function WCF_makeOutputMessageLink(aNode, aCallback)
+ function WCF_setMessageType(aMessageNode, aCategory, aSeverity)
{
- let linkNode;
- if (aNode.category === CATEGORY_NETWORK) {
- linkNode = aNode.querySelector(".webconsole-msg-link, .webconsole-msg-url");
- }
- else {
- linkNode = aNode.querySelector(".webconsole-msg-body");
- linkNode.classList.add("hud-clickable");
- }
-
- linkNode.setAttribute("aria-haspopup", "true");
-
- this._addMessageLinkCallback(aNode, aCallback);
+ aMessageNode.category = aCategory;
+ aMessageNode.severity = aSeverity;
+ aMessageNode.setAttribute("category", CATEGORY_CLASS_FRAGMENTS[aCategory]);
+ aMessageNode.setAttribute("severity", SEVERITY_CLASS_FRAGMENTS[aSeverity]);
+ aMessageNode.setAttribute("filter", MESSAGE_PREFERENCE_KEYS[aCategory][aSeverity]);
},
/**
@@ -2610,66 +2763,130 @@ WebConsoleFrame.prototype = {
*/
_addMessageLinkCallback: function WCF__addMessageLinkCallback(aNode, aCallback)
{
- aNode.addEventListener("mousedown", function(aEvent) {
+ aNode.addEventListener("mousedown", (aEvent) => {
+ this._mousedown = true;
+ this._startX = aEvent.clientX;
+ this._startY = aEvent.clientY;
+ }, false);
+
+ aNode.addEventListener("click", (aEvent) => {
+ let mousedown = this._mousedown;
+ this._mousedown = false;
+
+ aEvent.preventDefault();
+
+ // Do not allow middle/right-click or 2+ clicks.
+ if (aEvent.detail != 1 || aEvent.button != 0) {
+ return;
+ }
+
+ // If this event started with a mousedown event and it ends at a different
+ // location, we consider this text selection.
+ if (mousedown &&
+ (this._startX != aEvent.clientX) &&
+ (this._startY != aEvent.clientY))
+ {
+ this._startX = this._startY = undefined;
+ return;
+ }
+
+ this._startX = this._startY = undefined;
+
+ aCallback.call(this, aEvent);
+ }, false);
+ },
+
+ _addFocusCallback: function WCF__addFocusCallback(aNode, aCallback)
+ {
+ aNode.addEventListener("mousedown", (aEvent) => {
+ this._mousedown = true;
this._startX = aEvent.clientX;
this._startY = aEvent.clientY;
}, false);
- aNode.addEventListener("click", function(aEvent) {
- if (aEvent.detail != 1 || aEvent.button != 0 ||
- (this._startX != aEvent.clientX &&
- this._startY != aEvent.clientY)) {
+ aNode.addEventListener("click", (aEvent) => {
+ let mousedown = this._mousedown;
+ this._mousedown = false;
+
+ // Do not allow middle/right-click or 2+ clicks.
+ if (aEvent.detail != 1 || aEvent.button != 0) {
+ return;
+ }
+
+ // If this event started with a mousedown event and it ends at a different
+ // location, we consider this text selection.
+ // Add a fuzz modifier of two pixels in any direction to account for sloppy
+ // clicking.
+ if (mousedown &&
+ (Math.abs(aEvent.clientX - this._startX) >= 2) &&
+ (Math.abs(aEvent.clientY - this._startY) >= 1))
+ {
+ this._startX = this._startY = undefined;
return;
}
- aCallback(this, aEvent);
+ this._startX = this._startY = undefined;
+
+ aCallback.call(this, aEvent);
}, false);
},
/**
+ * Handler for the pref-changed event coming from the toolbox.
+ * Currently this function only handles the timestamps preferences.
+ *
+ * @private
+ * @param object aEvent
+ * This parameter is a string that holds the event name
+ * pref-changed in this case.
+ * @param object aData
+ * This is the pref-changed data object.
+ */
+ _onToolboxPrefChanged: function WCF__onToolboxPrefChanged(aEvent, aData)
+ {
+ if (aData.pref == PREF_MESSAGE_TIMESTAMP) {
+ if (aData.newValue) {
+ this.outputNode.classList.remove("hideTimestamps");
+ }
+ else {
+ this.outputNode.classList.add("hideTimestamps");
+ }
+ }
+ },
+
+ /**
* Copies the selected items to the system clipboard.
*
* @param object aOptions
* - linkOnly:
* An optional flag to copy only URL without timestamp and
* other meta-information. Default is false.
+ * - contextmenu:
+ * An optional flag to copy the last clicked item which brought
+ * up the context menu if nothing is selected. Default is false.
*/
copySelectedItems: function WCF_copySelectedItems(aOptions)
{
- aOptions = aOptions || { linkOnly: false };
+ aOptions = aOptions || { linkOnly: false, contextmenu: false };
// Gather up the selected items and concatenate their clipboard text.
let strings = [];
- let newGroup = false;
-
- let children = this.outputNode.children;
-
- for (let i = 0; i < children.length; i++) {
- let item = children[i];
- if (!item.selected) {
- continue;
- }
- // Add dashes between groups so that group boundaries show up in the
- // copied output.
- if (i > 0 && item.classList.contains("webconsole-new-group")) {
- newGroup = true;
- }
+ let children = this.output.getSelectedMessages();
+ if (!children.length && aOptions.contextmenu) {
+ children = [this._contextMenuHandler.lastClickedMessage];
+ }
+ for (let item of children) {
// Ensure the selected item hasn't been filtered by type or string.
- if (!item.classList.contains("hud-filtered-by-type") &&
- !item.classList.contains("hud-filtered-by-string")) {
+ if (!item.classList.contains("filtered-by-type") &&
+ !item.classList.contains("filtered-by-string")) {
let timestampString = l10n.timestampString(item.timestamp);
- if (newGroup) {
- strings.push("--");
- newGroup = false;
- }
-
if (aOptions.linkOnly) {
strings.push(item.url);
}
else {
- strings.push("[" + timestampString + "] " + item.clipboardText);
+ strings.push(item.clipboardText);
}
}
}
@@ -2719,7 +2936,8 @@ WebConsoleFrame.prototype = {
*/
openSelectedItemInTab: function WCF_openSelectedItemInTab()
{
- let item = this.outputNode.selectedItem;
+ let item = this.output.getSelectedMessages(1)[0] ||
+ this._contextMenuHandler.lastClickedMessage;
if (!item || !item.url) {
return;
@@ -2744,8 +2962,18 @@ WebConsoleFrame.prototype = {
this._destroyer = promise.defer();
+ let toolbox = gDevTools.getToolbox(this.owner.target);
+ if (toolbox) {
+ toolbox.off("webconsole-selected", this._onPanelSelected);
+ }
+
+ gDevTools.off("pref-changed", this._onToolboxPrefChanged);
+
this._repeatNodes = {};
+ this._outputQueue.forEach(this._destroyItem, this);
this._outputQueue = [];
+ this._itemDestroyQueue.forEach(this._destroyItem, this);
+ this._itemDestroyQueue = [];
this._pruneCategoriesQueue = {};
this._networkRequests = {};
@@ -2754,17 +2982,23 @@ WebConsoleFrame.prototype = {
this._outputTimer.cancel();
}
this._outputTimer = null;
-
if (this.jsterm) {
this.jsterm.destroy();
this.jsterm = null;
}
+ this.output.destroy();
+ this.output = null;
+
+ if (this._contextMenuHandler) {
+ this._contextMenuHandler.destroy();
+ this._contextMenuHandler = null;
+ }
this._commandController = null;
- let onDestroy = function() {
+ let onDestroy = () => {
this._destroyer.resolve(null);
- }.bind(this);
+ };
if (this.proxy) {
this.proxy.disconnect().then(onDestroy);
@@ -2837,6 +3071,7 @@ function JSTerm(aWebConsoleFrame)
this._inputEventHandler = this._inputEventHandler.bind(this);
this._focusEventHandler = this._focusEventHandler.bind(this);
this._onKeypressInVariablesView = this._onKeypressInVariablesView.bind(this);
+ this._blurEventHandler = this._blurEventHandler.bind(this);
EventEmitter.decorate(this);
}
@@ -2851,6 +3086,29 @@ JSTerm.prototype = {
lastCompletion: null,
/**
+ * Array that caches the user input suggestions received from the server.
+ * @private
+ * @type array
+ */
+ _autocompleteCache: null,
+
+ /**
+ * The input that caused the last request to the server, whose response is
+ * cached in the _autocompleteCache array.
+ * @private
+ * @type string
+ */
+ _autocompleteQuery: null,
+
+ /**
+ * The frameActorId used in the last autocomplete query. Whenever this changes
+ * the autocomplete cache must be invalidated.
+ * @private
+ * @type string
+ */
+ _lastFrameActorId: null,
+
+ /**
* The Web Console sidebar.
* @see this._createSidebar()
* @see Sidebar.jsm
@@ -2929,34 +3187,51 @@ JSTerm.prototype = {
COMPLETE_FORWARD: 0,
COMPLETE_BACKWARD: 1,
COMPLETE_HINT_ONLY: 2,
+ COMPLETE_PAGEUP: 3,
+ COMPLETE_PAGEDOWN: 4,
/**
* Initialize the JSTerminal UI.
*/
init: function JST_init()
{
- let chromeDocument = this.hud.owner.chromeWindow.document;
let autocompleteOptions = {
onSelect: this.onAutocompleteSelect.bind(this),
onClick: this.acceptProposedCompletion.bind(this),
panelId: "webConsole_autocompletePopup",
listBoxId: "webConsole_autocompletePopupListBox",
position: "before_start",
- theme: "light",
+ theme: "auto",
direction: "ltr",
autoSelect: true
};
- this.autocompletePopup = new AutocompletePopup(chromeDocument,
+ this.autocompletePopup = new AutocompletePopup(this.hud.document,
autocompleteOptions);
let doc = this.hud.document;
+ let inputContainer = doc.querySelector(".jsterm-input-container");
this.completeNode = doc.querySelector(".jsterm-complete-node");
this.inputNode = doc.querySelector(".jsterm-input-node");
- this.inputNode.addEventListener("keypress", this._keyPress, false);
- this.inputNode.addEventListener("input", this._inputEventHandler, false);
- this.inputNode.addEventListener("keyup", this._inputEventHandler, false);
- this.inputNode.addEventListener("focus", this._focusEventHandler, false);
+ if (this.hud.owner._browserConsole &&
+ !Services.prefs.getBoolPref("devtools.chrome.enabled")) {
+ inputContainer.style.display = "none";
+ }
+ else {
+ let okstring = l10n.getStr("selfxss.okstring");
+ let msg = l10n.getFormatStr("selfxss.msg", [okstring]);
+ this._onPaste = WebConsoleUtils.pasteHandlerGen(this.inputNode,
+ doc.getElementById("webconsole-notificationbox"),
+ msg, okstring);
+ this.inputNode.addEventListener("keypress", this._keyPress, false);
+ this.inputNode.addEventListener("paste", this._onPaste);
+ this.inputNode.addEventListener("drop", this._onPaste);
+ this.inputNode.addEventListener("input", this._inputEventHandler, false);
+ this.inputNode.addEventListener("keyup", this._inputEventHandler, false);
+ this.inputNode.addEventListener("focus", this._focusEventHandler, false);
+ }
+
+ this.hud.window.addEventListener("blur", this._blurEventHandler, false);
this.lastInputValue && this.setInputValue(this.lastInputValue);
},
@@ -2964,8 +3239,8 @@ JSTerm.prototype = {
* The JavaScript evaluation response handler.
*
* @private
- * @param nsIDOMElement [aAfterNode]
- * Optional DOM element after which the evaluation result will be
+ * @param object [aAfterMessage]
+ * Optional message after which the evaluation result will be
* inserted.
* @param function [aCallback]
* Optional function to invoke when the evaluation result is added to
@@ -2974,7 +3249,7 @@ JSTerm.prototype = {
* The message received from the server.
*/
_executeResultCallback:
- function JST__executeResultCallback(aAfterNode, aCallback, aResponse)
+ function JST__executeResultCallback(aAfterMessage, aCallback, aResponse)
{
if (!this.hud) {
return;
@@ -2986,13 +3261,8 @@ JSTerm.prototype = {
}
let errorMessage = aResponse.exceptionMessage;
let result = aResponse.result;
- let inspectable = false;
- if (result && !VariablesView.isPrimitive({ value: result })) {
- inspectable = true;
- }
let helperResult = aResponse.helperResult;
let helperHasRawOutput = !!(helperResult || {}).rawOutput;
- let resultString = VariablesView.getString(result);
if (helperResult && helperResult.type) {
switch (helperResult.type) {
@@ -3000,14 +3270,14 @@ JSTerm.prototype = {
this.clearOutput();
break;
case "inspectObject":
- if (aAfterNode) {
- if (!aAfterNode._objectActors) {
- aAfterNode._objectActors = new Set();
+ if (aAfterMessage) {
+ if (!aAfterMessage._objectActors) {
+ aAfterMessage._objectActors = new Set();
}
- aAfterNode._objectActors.add(helperResult.object.actor);
+ aAfterMessage._objectActors.add(helperResult.object.actor);
}
this.openVariablesView({
- label: VariablesView.getString(helperResult.object),
+ label: VariablesView.getString(helperResult.object, { concise: true }),
objectActor: helperResult.object,
});
break;
@@ -3022,6 +3292,9 @@ JSTerm.prototype = {
case "help":
this.hud.owner.openLink(HELP_URL);
break;
+ case "copyValueToClipboard":
+ clipboardHelper.copyString(helperResult.value);
+ break;
}
}
@@ -3033,60 +3306,32 @@ JSTerm.prototype = {
return;
}
+ let msg = new Messages.JavaScriptEvalOutput(aResponse, errorMessage);
+ this.hud.output.addMessage(msg);
+
if (aCallback) {
let oldFlushCallback = this.hud._flushCallback;
- this.hud._flushCallback = function() {
- aCallback();
- oldFlushCallback && oldFlushCallback();
- this.hud._flushCallback = oldFlushCallback;
- }.bind(this);
- }
-
- let node;
+ this.hud._flushCallback = () => {
+ aCallback(msg.element);
+ if (oldFlushCallback) {
+ oldFlushCallback();
+ this.hud._flushCallback = oldFlushCallback;
+ return true;
+ }
- if (errorMessage) {
- node = this.writeOutput(errorMessage, CATEGORY_OUTPUT, SEVERITY_ERROR,
- aAfterNode, aResponse.timestamp);
- }
- else if (inspectable) {
- node = this.writeOutputJS(resultString,
- this._evalOutputClick.bind(this, aResponse),
- aAfterNode, aResponse.timestamp);
- }
- else {
- node = this.writeOutput(resultString, CATEGORY_OUTPUT, SEVERITY_LOG,
- aAfterNode, aResponse.timestamp);
+ return false;
+ };
}
- node._objectActors = new Set();
+ msg._afterMessage = aAfterMessage;
+ msg._objectActors = new Set();
- let error = aResponse.exception;
- if (WebConsoleUtils.isActorGrip(error)) {
- node._objectActors.add(error.actor);
+ if (WebConsoleUtils.isActorGrip(aResponse.exception)) {
+ msg._objectActors.add(aResponse.exception.actor);
}
if (WebConsoleUtils.isActorGrip(result)) {
- node._objectActors.add(result.actor);
-
- if (result.type == "longString") {
- // Add an ellipsis to expand the short string if the object is not
- // inspectable.
-
- let body = node.querySelector(".webconsole-msg-body");
- let ellipsis = this.hud.document.createElement("description");
- ellipsis.classList.add("hud-clickable");
- ellipsis.classList.add("longStringEllipsis");
- ellipsis.textContent = l10n.getStr("longStringEllipsis");
-
- let formatter = function(s) '"' + s + '"';
- let onclick = this.hud._longStringClick.bind(this.hud, node, result,
- formatter);
- this.hud._addMessageLinkCallback(ellipsis, onclick);
-
- body.appendChild(ellipsis);
-
- node.clipboardText += " " + ellipsis.textContent;
- }
+ msg._objectActors.add(result.actor);
}
},
@@ -3098,19 +3343,44 @@ JSTerm.prototype = {
* user input is used - taken from |this.inputNode.value|.
* @param function [aCallback]
* Optional function to invoke when the result is displayed.
+ * This is deprecated - please use the promise return value instead.
+ * @returns Promise
+ * Resolves with the message once the result is displayed.
*/
execute: function JST_execute(aExecuteString, aCallback)
{
+ let deferred = promise.defer();
+ let callback = function(msg) {
+ deferred.resolve(msg);
+ if (aCallback) {
+ aCallback(msg);
+ }
+ }
+
// attempt to execute the content of the inputNode
aExecuteString = aExecuteString || this.inputNode.value;
if (!aExecuteString) {
return;
}
- let node = this.writeOutput(aExecuteString, CATEGORY_INPUT, SEVERITY_LOG);
- let onResult = this._executeResultCallback.bind(this, node, aCallback);
+ let selectedNodeActor = null;
+ let inspectorSelection = this.hud.owner.getInspectorSelection();
+ if (inspectorSelection) {
+ selectedNodeActor = inspectorSelection.nodeFront.actorID;
+ }
+
+ let message = new Messages.Simple(aExecuteString, {
+ category: "input",
+ severity: "log",
+ });
+ this.hud.output.addMessage(message);
+ let onResult = this._executeResultCallback.bind(this, message, callback);
+
+ let options = {
+ frame: this.SELECTED_FRAME,
+ selectedNodeActor: selectedNodeActor,
+ };
- let options = { frame: this.SELECTED_FRAME };
this.requestEvaluation(aExecuteString, options).then(onResult, onResult);
// Append a new value in the history of executed code, or overwrite the most
@@ -3118,8 +3388,10 @@ JSTerm.prototype = {
// value that was not evaluated yet.
this.history[this.historyIndex++] = aExecuteString;
this.historyPlaceHolder = this.history.length;
+ WebConsoleUtils.usageCount++;
this.setInputValue("");
this.clearCompletion();
+ return deferred.promise;
},
/**
@@ -3139,6 +3411,9 @@ JSTerm.prototype = {
* user-selected stackframe.
* If you do not provide a |frame| the string will be evaluated in the
* global content window.
+ * - selectedNodeActor: tells the NodeActor ID of the current selection in
+ * the Inspector, if such a selection exists. This is used by helper
+ * functions that can evaluate on the current selection.
* @return object
* A promise object that is resolved when the server response is
* received.
@@ -3164,9 +3439,10 @@ JSTerm.prototype = {
let evalOptions = {
bindObjectActor: aOptions.bindObjectActor,
frameActor: frameActor,
+ selectedNodeActor: aOptions.selectedNodeActor,
};
- this.webConsoleClient.evaluateJS(aString, onResult, evalOptions);
+ this.webConsoleClient.evaluateJSAsync(aString, onResult, evalOptions);
return deferred.promise;
},
@@ -3249,14 +3525,16 @@ JSTerm.prototype = {
let deferred = promise.defer();
openPromise = deferred.promise;
let document = aOptions.targetElement.ownerDocument;
- let iframe = document.createElement("iframe");
+ let iframe = document.createElementNS(XHTML_NS, "iframe");
iframe.addEventListener("load", function onIframeLoad(aEvent) {
iframe.removeEventListener("load", onIframeLoad, true);
+ iframe.style.visibility = "visible";
deferred.resolve(iframe.contentWindow);
}, true);
iframe.flex = 1;
+ iframe.style.visibility = "hidden";
iframe.setAttribute("src", VARIABLES_VIEW_URL);
aOptions.targetElement.appendChild(iframe);
}
@@ -3279,7 +3557,6 @@ JSTerm.prototype = {
_createSidebar: function JST__createSidebar()
{
let tabbox = this.hud.document.querySelector("#webconsole-sidebar");
- let ToolSidebar = devtools.require("devtools/framework/sidebar").ToolSidebar;
this.sidebar = new ToolSidebar(tabbox, this, "webconsole");
this.sidebar.show();
},
@@ -3300,8 +3577,8 @@ JSTerm.prototype = {
deferred.resolve(window);
};
- let tab = this.sidebar.getTab("variablesview");
- if (tab) {
+ let tabPanel = this.sidebar.getTabPanel("variablesview");
+ if (tabPanel) {
if (this.sidebar.getCurrentTabID() == "variablesview") {
onTabReady();
}
@@ -3337,6 +3614,7 @@ JSTerm.prototype = {
this._sidebarDestroy();
this.inputNode.focus();
+ aEvent.stopPropagation();
},
/**
@@ -3354,15 +3632,18 @@ JSTerm.prototype = {
_createVariablesView: function JST__createVariablesView(aOptions)
{
let view = new VariablesView(aOptions.container);
+ view.toolbox = gDevTools.getToolbox(this.hud.owner.target);
view.searchPlaceholder = l10n.getStr("propertiesFilterPlaceholder");
view.emptyText = l10n.getStr("emptyPropertiesList");
view.searchEnabled = !aOptions.hideFilterInput;
view.lazyEmpty = this._lazyVariablesView;
- view.lazyAppend = this._lazyVariablesView;
VariablesViewController.attach(view, {
- getGripClient: aGrip => {
- return new GripClient(this.hud.proxy.client, aGrip);
+ getEnvironmentClient: aGrip => {
+ return new EnvironmentClient(this.hud.proxy.client, aGrip);
+ },
+ getObjectClient: aGrip => {
+ return new ObjectClient(this.hud.proxy.client, aGrip);
},
getLongStringClient: aGrip => {
return this.webConsoleClient.longString(aGrip);
@@ -3398,7 +3679,6 @@ JSTerm.prototype = {
_updateVariablesView: function JST__updateVariablesView(aOptions)
{
let view = aOptions.view;
- view.createHierarchy();
view.empty();
// We need to avoid pruning the object inspection starting point.
@@ -3407,7 +3687,9 @@ JSTerm.prototype = {
return view._consoleLastObjectActor != aActor;
});
- if (aOptions.objectActor) {
+ if (aOptions.objectActor &&
+ (!this.hud.owner._browserConsole ||
+ Services.prefs.getBoolPref("devtools.chrome.enabled"))) {
// Make sure eval works in the correct context.
view.eval = this._variablesViewEvaluate.bind(this, aOptions);
view.switch = this._variablesViewSwitch.bind(this, aOptions);
@@ -3419,20 +3701,13 @@ JSTerm.prototype = {
view.delete = null;
}
- let scope = view.addScope(aOptions.label);
- scope.expanded = true;
- scope.locked = true;
-
- let container = scope.addItem();
- container.evaluationMacro = simpleValueEvalMacro;
+ let { variable, expanded } = view.controller.setSingleVariable(aOptions);
+ variable.evaluationMacro = simpleValueEvalMacro;
if (aOptions.objectActor) {
- view.controller.expand(container, aOptions.objectActor);
view._consoleLastObjectActor = aOptions.objectActor.actor;
}
else if (aOptions.rawObject) {
- container.populate(aOptions.rawObject);
- view.commitHierarchy();
view._consoleLastObjectActor = null;
}
else {
@@ -3440,7 +3715,9 @@ JSTerm.prototype = {
"display.");
}
- this.emit("variablesview-updated", view, aOptions);
+ expanded.then(() => {
+ this.emit("variablesview-updated", view, aOptions);
+ });
},
/**
@@ -3450,20 +3727,24 @@ JSTerm.prototype = {
* @private
* @param object aOptions
* The options used for |this._updateVariablesView()|.
- * @param string aString
- * The string that the variables view wants to evaluate.
+ * @param object aVar
+ * The Variable object instance for the edited property.
+ * @param string aValue
+ * The value the edited property was changed to.
*/
- _variablesViewEvaluate: function JST__variablesViewEvaluate(aOptions, aString)
+ _variablesViewEvaluate:
+ function JST__variablesViewEvaluate(aOptions, aVar, aValue)
{
let updater = this._updateVariablesView.bind(this, aOptions);
let onEval = this._silentEvalCallback.bind(this, updater);
+ let string = aVar.evaluationMacro(aVar, aValue);
let evalOptions = {
frame: this.SELECTED_FRAME,
bindObjectActor: aOptions.objectActor.actor,
};
- this.requestEvaluation(aString, evalOptions).then(onEval, onEval);
+ this.requestEvaluation(string, evalOptions).then(onEval, onEval);
},
/**
@@ -3547,14 +3828,16 @@ JSTerm.prototype = {
return;
}
- let exception = aResponse.exception;
- if (exception) {
- let node = this.writeOutput(aResponse.exceptionMessage,
- CATEGORY_OUTPUT, SEVERITY_ERROR,
- null, aResponse.timestamp);
- node._objectActors = new Set();
- if (WebConsoleUtils.isActorGrip(exception)) {
- node._objectActors.add(exception.actor);
+ if (aResponse.exceptionMessage) {
+ let message = new Messages.Simple(aResponse.exceptionMessage, {
+ category: "output",
+ severity: "error",
+ timestamp: aResponse.timestamp,
+ });
+ this.hud.output.addMessage(message);
+ message._objectActors = new Set();
+ if (WebConsoleUtils.isActorGrip(aResponse.exception)) {
+ message._objectActors.add(aResponse.exception.actor);
}
}
@@ -3575,68 +3858,6 @@ JSTerm.prototype = {
},
-
- /**
- * Writes a JS object to the JSTerm outputNode.
- *
- * @param string aOutputMessage
- * The message to display.
- * @param function [aCallback]
- * Optional function to invoke when users click the message.
- * @param nsIDOMNode [aNodeAfter]
- * Optional DOM node after which you want to insert the new message.
- * This is used when execution results need to be inserted immediately
- * after the user input.
- * @param number [aTimestamp]
- * Optional timestamp to show for the output message (millisconds since
- * the UNIX epoch). If no timestamp is provided then Date.now() is
- * used.
- * @return nsIDOMNode
- * The new message node.
- */
- writeOutputJS:
- function JST_writeOutputJS(aOutputMessage, aCallback, aNodeAfter, aTimestamp)
- {
- let node = this.writeOutput(aOutputMessage, CATEGORY_OUTPUT, SEVERITY_LOG,
- aNodeAfter, aTimestamp);
- if (aCallback) {
- this.hud.makeOutputMessageLink(node, aCallback);
- }
- return node;
- },
-
- /**
- * Writes a message to the HUD that originates from the interactive
- * JavaScript console.
- *
- * @param string aOutputMessage
- * The message to display.
- * @param number aCategory
- * The category of message: one of the CATEGORY_ constants.
- * @param number aSeverity
- * The severity of message: one of the SEVERITY_ constants.
- * @param nsIDOMNode [aNodeAfter]
- * Optional DOM node after which you want to insert the new message.
- * This is used when execution results need to be inserted immediately
- * after the user input.
- * @param number [aTimestamp]
- * Optional timestamp to show for the output message (millisconds since
- * the UNIX epoch). If no timestamp is provided then Date.now() is
- * used.
- * @return nsIDOMNode
- * The new message node.
- */
- writeOutput:
- function JST_writeOutput(aOutputMessage, aCategory, aSeverity, aNodeAfter,
- aTimestamp)
- {
- let node = this.hud.createMessageNode(aCategory, aSeverity, aOutputMessage,
- null, null, null, null, aTimestamp);
- node._outputAfterNode = aNodeAfter;
- this.hud.outputMessage(aCategory, node);
- return node;
- },
-
/**
* Clear the Web Console output.
*
@@ -3656,7 +3877,7 @@ JSTerm.prototype = {
}
hud.groupDepth = 0;
- hud._outputQueue.forEach(hud._pruneItemFromQueue, hud);
+ hud._outputQueue.forEach(hud._destroyItem, hud);
hud._outputQueue = [];
hud._networkRequests = {};
hud._repeatNodes = {};
@@ -3675,7 +3896,7 @@ JSTerm.prototype = {
*/
clearPrivateMessages: function JST_clearPrivateMessages()
{
- let nodes = this.hud.outputNode.querySelectorAll("richlistitem[private]");
+ let nodes = this.hud.outputNode.querySelectorAll(".message[private]");
for (let node of nodes) {
this.hud.removeOutputMessage(node);
}
@@ -3735,6 +3956,17 @@ JSTerm.prototype = {
},
/**
+ * The window "blur" event handler.
+ * @private
+ */
+ _blurEventHandler: function JST__blurEventHandler()
+ {
+ if (this.autocompletePopup) {
+ this.clearCompletion();
+ }
+ },
+
+ /**
* The inputNode "keypress" event handler.
*
* @private
@@ -3747,30 +3979,6 @@ JSTerm.prototype = {
if (aEvent.ctrlKey) {
switch (aEvent.charCode) {
- case 97:
- // control-a
- this.clearCompletion();
-
- if (Services.appinfo.OS == "WINNT") {
- // Allow Select All on Windows.
- break;
- }
-
- let lineBeginPos = 0;
- if (this.hasMultilineInput()) {
- // find index of closest newline <= to cursor
- for (let i = inputNode.selectionStart-1; i >= 0; i--) {
- if (inputNode.value.charAt(i) == "\r" ||
- inputNode.value.charAt(i) == "\n") {
- lineBeginPos = i+1;
- break;
- }
- }
- }
- inputNode.setSelectionRange(lineBeginPos, lineBeginPos);
- aEvent.preventDefault();
- break;
-
case 101:
// control-e
if (Services.appinfo.OS == "WINNT") {
@@ -3840,10 +4048,12 @@ JSTerm.prototype = {
if (this.autocompletePopup.isOpen) {
this.clearCompletion();
aEvent.preventDefault();
+ aEvent.stopPropagation();
}
else if (this.sidebar) {
this._sidebarDestroy();
aEvent.preventDefault();
+ aEvent.stopPropagation();
}
break;
@@ -3890,8 +4100,60 @@ JSTerm.prototype = {
}
break;
+ case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP:
+ if (this.autocompletePopup.isOpen) {
+ inputUpdated = this.complete(this.COMPLETE_PAGEUP);
+ if (inputUpdated) {
+ this._autocompletePopupNavigated = true;
+ }
+ }
+ else {
+ this.hud.outputNode.parentNode.scrollTop =
+ Math.max(0,
+ this.hud.outputNode.parentNode.scrollTop -
+ this.hud.outputNode.parentNode.clientHeight
+ );
+ }
+ aEvent.preventDefault();
+ break;
+
+ case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN:
+ if (this.autocompletePopup.isOpen) {
+ inputUpdated = this.complete(this.COMPLETE_PAGEDOWN);
+ if (inputUpdated) {
+ this._autocompletePopupNavigated = true;
+ }
+ }
+ else {
+ this.hud.outputNode.parentNode.scrollTop =
+ Math.min(this.hud.outputNode.parentNode.scrollHeight,
+ this.hud.outputNode.parentNode.scrollTop +
+ this.hud.outputNode.parentNode.clientHeight
+ );
+ }
+ aEvent.preventDefault();
+ break;
+
case Ci.nsIDOMKeyEvent.DOM_VK_HOME:
+ if (this.autocompletePopup.isOpen) {
+ this.autocompletePopup.selectedIndex = 0;
+ aEvent.preventDefault();
+ } else if (this.inputNode.value.length <= 0) {
+ this.hud.outputNode.parentNode.scrollTop = 0;
+ aEvent.preventDefault();
+ }
+ break;
+
case Ci.nsIDOMKeyEvent.DOM_VK_END:
+ if (this.autocompletePopup.isOpen) {
+ this.autocompletePopup.selectedIndex = this.autocompletePopup.itemCount - 1;
+ aEvent.preventDefault();
+ } else if (this.inputNode.value.length <= 0) {
+ this.hud.outputNode.parentNode.scrollTop = this.hud.outputNode.parentNode.scrollHeight;
+ aEvent.preventDefault();
+ }
+ break;
+
case Ci.nsIDOMKeyEvent.DOM_VK_LEFT:
if (this.autocompletePopup.isOpen || this.lastCompletion.value) {
this.clearCompletion();
@@ -4061,6 +4323,10 @@ JSTerm.prototype = {
* - this.COMPLETE_BACKWARD: Same as this.COMPLETE_FORWARD but if the
* value stayed the same as the last time the function was called,
* then the previous completion of all possible completions is used.
+ * - this.COMPLETE_PAGEUP: Scroll up one page if available or select the first
+ * item.
+ * - this.COMPLETE_PAGEDOWN: Scroll down one page if available or select the
+ * last item.
* - this.COMPLETE_HINT_ONLY: If there is more than one possible
* completion and the input value stayed the same compared to the
* last time this function was called, then the same completion is
@@ -4077,21 +4343,26 @@ JSTerm.prototype = {
{
let inputNode = this.inputNode;
let inputValue = inputNode.value;
+ let frameActor = this.getFrameActor(this.SELECTED_FRAME);
+
// If the inputNode has no value, then don't try to complete on it.
if (!inputValue) {
this.clearCompletion();
+ aCallback && aCallback(this);
+ this.emit("autocomplete-updated");
return false;
}
- // Only complete if the selection is empty and at the end of the input.
- if (inputNode.selectionStart == inputNode.selectionEnd &&
- inputNode.selectionEnd != inputValue.length) {
+ // Only complete if the selection is empty.
+ if (inputNode.selectionStart != inputNode.selectionEnd) {
this.clearCompletion();
+ aCallback && aCallback(this);
+ this.emit("autocomplete-updated");
return false;
}
// Update the completion results.
- if (this.lastCompletion.value != inputValue) {
+ if (this.lastCompletion.value != inputValue || frameActor != this._lastFrameActorId) {
this._updateCompletionResult(aType, aCallback);
return false;
}
@@ -4109,8 +4380,15 @@ JSTerm.prototype = {
else if (aType == this.COMPLETE_FORWARD) {
popup.selectNextItem();
}
+ else if (aType == this.COMPLETE_PAGEUP) {
+ popup.selectPreviousPageItem();
+ }
+ else if (aType == this.COMPLETE_PAGEDOWN) {
+ popup.selectNextPageItem();
+ }
aCallback && aCallback(this);
+ this.emit("autocomplete-updated");
return accepted || popup.itemCount > 0;
},
@@ -4127,16 +4405,54 @@ JSTerm.prototype = {
_updateCompletionResult:
function JST__updateCompletionResult(aType, aCallback)
{
- if (this.lastCompletion.value == this.inputNode.value) {
+ let frameActor = this.getFrameActor(this.SELECTED_FRAME);
+ if (this.lastCompletion.value == this.inputNode.value && frameActor == this._lastFrameActorId) {
return;
}
let requestId = gSequenceId();
- let input = this.inputNode.value;
let cursor = this.inputNode.selectionStart;
+ let input = this.inputNode.value.substring(0, cursor);
+ let cache = this._autocompleteCache;
+
+ // If the current input starts with the previous input, then we already
+ // have a list of suggestions and we just need to filter the cached
+ // suggestions. When the current input ends with a non-alphanumeric
+ // character we ask the server again for suggestions.
+
+ // Check if last character is non-alphanumeric
+ if (!/[a-zA-Z0-9]$/.test(input) || frameActor != this._lastFrameActorId) {
+ this._autocompleteQuery = null;
+ this._autocompleteCache = null;
+ }
+
+ if (this._autocompleteQuery && input.startsWith(this._autocompleteQuery)) {
+ let filterBy = input;
+ // Find the last non-alphanumeric other than _ or $ if it exists.
+ let lastNonAlpha = input.match(/[^a-zA-Z0-9_$][a-zA-Z0-9_$]*$/);
+ // If input contains non-alphanumerics, use the part after the last one
+ // to filter the cache
+ if (lastNonAlpha) {
+ filterBy = input.substring(input.lastIndexOf(lastNonAlpha) + 1);
+ }
+
+ let newList = cache.sort().filter(function(l) {
+ return l.startsWith(filterBy);
+ });
+
+ this.lastCompletion = {
+ requestId: null,
+ completionType: aType,
+ value: null,
+ };
+
+ let response = { matches: newList, matchProp: filterBy };
+ this._receiveAutocompleteProperties(null, aCallback, response);
+ return;
+ }
+
+ this._lastFrameActorId = frameActor;
- // TODO: Bug 787986 - throttle/disable updates, deal with slow/high latency
- // network connections.
this.lastCompletion = {
requestId: requestId,
completionType: aType,
@@ -4145,7 +4461,8 @@ JSTerm.prototype = {
let callback = this._receiveAutocompleteProperties.bind(this, requestId,
aCallback);
- this.webConsoleClient.autocomplete(input, cursor, callback);
+
+ this.webConsoleClient.autocomplete(input, cursor, callback, frameActor);
},
/**
@@ -4170,11 +4487,21 @@ JSTerm.prototype = {
aRequestId != this.lastCompletion.requestId) {
return;
}
+ // Cache whatever came from the server if the last char is alphanumeric or '.'
+ let cursor = inputNode.selectionStart;
+ let inputUntilCursor = inputValue.substring(0, cursor);
+
+ if (aRequestId != null && /[a-zA-Z0-9.]$/.test(inputUntilCursor)) {
+ this._autocompleteCache = aMessage.matches;
+ this._autocompleteQuery = inputUntilCursor;
+ }
let matches = aMessage.matches;
let lastPart = aMessage.matchProp;
if (!matches.length) {
this.clearCompletion();
+ aCallback && aCallback(this);
+ this.emit("autocomplete-updated");
return;
}
@@ -4192,7 +4519,10 @@ JSTerm.prototype = {
};
if (items.length > 1 && !popup.isOpen) {
- popup.openPopup(inputNode);
+ let str = this.inputNode.value.substr(0, this.inputNode.selectionStart);
+ let offset = str.length - (str.lastIndexOf("\n") + 1) - lastPart.length;
+ let x = offset * this.hud._inputCharWidth;
+ popup.openPopup(inputNode, x + this.hud._chevronWidth);
this._autocompletePopupNavigated = false;
}
else if (items.length < 2 && popup.isOpen) {
@@ -4217,10 +4547,16 @@ JSTerm.prototype = {
}
aCallback && aCallback(this);
+ this.emit("autocomplete-updated");
},
onAutocompleteSelect: function JSTF_onAutocompleteSelect()
{
+ // Render the suggestion only if the cursor is at the end of the input.
+ if (this.inputNode.selectionStart != this.inputNode.value.length) {
+ return;
+ }
+
let currentItem = this.autocompletePopup.selectedItem;
if (currentItem && this.lastCompletion.value) {
let suffix = currentItem.label.substring(this.lastCompletion.
@@ -4262,7 +4598,11 @@ JSTerm.prototype = {
if (currentItem && this.lastCompletion.value) {
let suffix = currentItem.label.substring(this.lastCompletion.
matchProp.length);
- this.setInputValue(this.inputNode.value + suffix);
+ let cursor = this.inputNode.selectionStart;
+ let value = this.inputNode.value;
+ this.setInputValue(value.substr(0, cursor) + suffix + value.substr(cursor));
+ let newCursor = cursor + suffix.length;
+ this.inputNode.selectionStart = this.inputNode.selectionEnd = newCursor;
updated = true;
}
@@ -4284,21 +4624,6 @@ JSTerm.prototype = {
this.completeNode.value = prefix + aSuffix;
},
- /**
- * The click event handler for evaluation results in the output.
- *
- * @private
- * @param object aResponse
- * The JavaScript evaluation response received from the server.
- */
- _evalOutputClick: function JST__evalOutputClick(aResponse)
- {
- this.openVariablesView({
- label: VariablesView.getString(aResponse.result),
- objectActor: aResponse.result,
- autofocus: true,
- });
- },
/**
* Destroy the sidebar.
@@ -4339,10 +4664,17 @@ JSTerm.prototype = {
popup.parentNode.removeChild(popup);
}
+ if (this._onPaste) {
+ this.inputNode.removeEventListener("paste", this._onPaste, false);
+ this.inputNode.removeEventListener("drop", this._onPaste, false);
+ this._onPaste = null;
+ }
+
this.inputNode.removeEventListener("keypress", this._keyPress, false);
this.inputNode.removeEventListener("input", this._inputEventHandler, false);
this.inputNode.removeEventListener("keyup", this._inputEventHandler, false);
this.inputNode.removeEventListener("focus", this._focusEventHandler, false);
+ this.hud.window.removeEventListener("blur", this._blurEventHandler, false);
this.hud = null;
},
@@ -4353,13 +4685,7 @@ JSTerm.prototype = {
*/
var Utils = {
/**
- * Flag to turn on and off scrolling.
- */
- scroll: true,
-
- /**
- * Scrolls a node so that it's visible in its containing XUL "scrollbox"
- * element.
+ * Scrolls a node so that it's visible in its containing element.
*
* @param nsIDOMNode aNode
* The node to make visible.
@@ -4367,20 +4693,7 @@ var Utils = {
*/
scrollToVisible: function Utils_scrollToVisible(aNode)
{
- if (!this.scroll) {
- return;
- }
-
- // Find the enclosing richlistbox node.
- let richListBoxNode = aNode.parentNode;
- while (richListBoxNode.tagName != "richlistbox") {
- richListBoxNode = richListBoxNode.parentNode;
- }
-
- // Use the scroll box object interface to ensure the element is visible.
- let boxObject = richListBoxNode.scrollBoxObject;
- let nsIScrollBoxObject = boxObject.QueryInterface(Ci.nsIScrollBoxObject);
- nsIScrollBoxObject.ensureElementIsVisible(aNode);
+ aNode.scrollIntoView(false);
},
/**
@@ -4395,10 +4708,9 @@ var Utils = {
{
let lastNodeHeight = aOutputNode.lastChild ?
aOutputNode.lastChild.clientHeight : 0;
- let scrollBox = aOutputNode.scrollBoxObject.element;
-
- return scrollBox.scrollTop + scrollBox.clientHeight >=
- scrollBox.scrollHeight - lastNodeHeight / 2;
+ let scrollNode = aOutputNode.parentNode;
+ return scrollNode.scrollTop + scrollNode.clientHeight >=
+ scrollNode.scrollHeight - lastNodeHeight / 2;
},
/**
@@ -4412,13 +4724,24 @@ var Utils = {
*/
categoryForScriptError: function Utils_categoryForScriptError(aScriptError)
{
- switch (aScriptError.category) {
- case "CSS Parser":
- case "CSS Loader":
- return CATEGORY_CSS;
+ let category = aScriptError.category;
+ if (/^(?:CSS|Layout)\b/.test(category)) {
+ return CATEGORY_CSS;
+ }
+
+ switch (category) {
case "Mixed Content Blocker":
+ case "Mixed Content Message":
case "CSP":
+ case "Invalid HSTS Headers":
+ case "Invalid HPKP Headers":
+ case "SHA-1 Signature":
+ case "Insecure Password Field":
+ case "SSL":
+ case "CORS":
+ case "Iframe Sandbox":
+ case "Tracking Protection":
return CATEGORY_SECURITY;
default:
@@ -4465,20 +4788,11 @@ function CommandController(aWebConsole)
CommandController.prototype = {
/**
- * Copies the currently-selected entries in the Web Console output to the
- * clipboard.
- */
- copy: function CommandController_copy()
- {
- this.owner.copySelectedItems();
- },
-
- /**
* Selects all the text in the HUD output.
*/
selectAll: function CommandController_selectAll()
{
- this.owner.outputNode.selectAll();
+ this.owner.output.selectAllMessages();
},
/**
@@ -4491,33 +4805,48 @@ CommandController.prototype = {
copyURL: function CommandController_copyURL()
{
- this.owner.copySelectedItems({ linkOnly: true });
+ this.owner.copySelectedItems({ linkOnly: true, contextmenu: true });
+ },
+
+ /**
+ * Copies the last clicked message.
+ */
+ copyLastClicked: function CommandController_copy()
+ {
+ this.owner.copySelectedItems({ linkOnly: false, contextmenu: true });
},
supportsCommand: function CommandController_supportsCommand(aCommand)
{
+ if (!this.owner || !this.owner.output) {
+ return false;
+ }
return this.isCommandEnabled(aCommand);
},
isCommandEnabled: function CommandController_isCommandEnabled(aCommand)
{
switch (aCommand) {
- case "cmd_copy":
- // Only enable "copy" if nodes are selected.
- return this.owner.outputNode.selectedCount > 0;
case "consoleCmd_openURL":
case "consoleCmd_copyURL": {
// Only enable URL-related actions if node is Net Activity.
- let selectedItem = this.owner.outputNode.selectedItem;
+ let selectedItem = this.owner.output.getSelectedMessages(1)[0] ||
+ this.owner._contextMenuHandler.lastClickedMessage;
return selectedItem && "url" in selectedItem;
}
+ case "cmd_copy": {
+ // Only copy if we right-clicked the console and there's no selected text.
+ // With text selected, we want to fall back onto the default copy behavior.
+ return this.owner._contextMenuHandler.lastClickedMessage &&
+ !this.owner.output.getSelectedMessages(1)[0];
+ }
case "consoleCmd_clearOutput":
- case "cmd_fontSizeEnlarge":
- case "cmd_fontSizeReduce":
- case "cmd_fontSizeReset":
case "cmd_selectAll":
case "cmd_find":
return true;
+ case "cmd_fontSizeEnlarge":
+ case "cmd_fontSizeReduce":
+ case "cmd_fontSizeReset":
case "cmd_close":
return this.owner.owner._browserConsole;
}
@@ -4527,9 +4856,6 @@ CommandController.prototype = {
doCommand: function CommandController_doCommand(aCommand)
{
switch (aCommand) {
- case "cmd_copy":
- this.copy();
- break;
case "consoleCmd_openURL":
this.openURL();
break;
@@ -4539,6 +4865,9 @@ CommandController.prototype = {
case "consoleCmd_clearOutput":
this.owner.jsterm.clearOutput(true);
break;
+ case "cmd_copy":
+ this.copyLastClicked();
+ break;
case "cmd_find":
this.owner.filterBox.focus();
break;
@@ -4586,6 +4915,7 @@ function WebConsoleConnectionProxy(aWebConsole, aTarget)
this._onNetworkEvent = this._onNetworkEvent.bind(this);
this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
this._onFileActivity = this._onFileActivity.bind(this);
+ this._onReflowActivity = this._onReflowActivity.bind(this);
this._onTabNavigated = this._onTabNavigated.bind(this);
this._onAttachConsole = this._onAttachConsole.bind(this);
this._onCachedMessages = this._onCachedMessages.bind(this);
@@ -4677,12 +5007,12 @@ WebConsoleConnectionProxy.prototype = {
timeout, Ci.nsITimer.TYPE_ONE_SHOT);
let connPromise = this._connectDefer.promise;
- connPromise.then(function _onSucess() {
+ connPromise.then(() => {
this._connectTimer.cancel();
this._connectTimer = null;
- }.bind(this), function _onFailure() {
+ }, () => {
this._connectTimer = null;
- }.bind(this));
+ });
let client = this.client = this.target.client;
@@ -4692,6 +5022,7 @@ WebConsoleConnectionProxy.prototype = {
client.addListener("networkEvent", this._onNetworkEvent);
client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
client.addListener("fileActivity", this._onFileActivity);
+ client.addListener("reflowActivity", this._onReflowActivity);
client.addListener("lastPrivateContextExited", this._onLastPrivateContextExited);
this.target.on("will-navigate", this._onTabNavigated);
this.target.on("navigate", this._onTabNavigated);
@@ -4757,6 +5088,8 @@ WebConsoleConnectionProxy.prototype = {
let msgs = ["PageError", "ConsoleAPI"];
this.webConsoleClient.getCachedMessages(msgs, this._onCachedMessages);
+
+ this.owner._updateReflowActivityListener();
},
/**
@@ -4894,6 +5227,13 @@ WebConsoleConnectionProxy.prototype = {
}
},
+ _onReflowActivity: function WCCP__onReflowActivity(aType, aPacket)
+ {
+ if (this.owner && aPacket.from == this._consoleActor) {
+ this.owner.handleReflowActivity(aPacket);
+ }
+ },
+
/**
* The "lastPrivateContextExited" message type handler. When this message is
* received the Web Console UI is cleared.
@@ -4928,17 +5268,7 @@ WebConsoleConnectionProxy.prototype = {
return;
}
- if (aEvent == "will-navigate" && !this.owner.persistLog) {
- this.owner.jsterm.clearOutput();
- }
-
- if (aPacket.url) {
- this.owner.onLocationChange(aPacket.url, aPacket.title);
- }
-
- if (aEvent == "navigate" && !aPacket.nativeConsoleAPI) {
- this.owner.logWarningAboutReplacedAPI();
- }
+ this.owner.handleTabNavigated(aEvent, aPacket);
},
/**
@@ -4979,6 +5309,7 @@ WebConsoleConnectionProxy.prototype = {
this.client.removeListener("networkEvent", this._onNetworkEvent);
this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
this.client.removeListener("fileActivity", this._onFileActivity);
+ this.client.removeListener("reflowActivity", this._onReflowActivity);
this.client.removeListener("lastPrivateContextExited", this._onLastPrivateContextExited);
this.target.off("will-navigate", this._onTabNavigated);
this.target.off("navigate", this._onTabNavigated);
@@ -5000,41 +5331,35 @@ function gSequenceId()
}
gSequenceId.n = 0;
-
-function goUpdateConsoleCommands() {
- goUpdateCommand("consoleCmd_openURL");
- goUpdateCommand("consoleCmd_copyURL");
-}
-
-
-
///////////////////////////////////////////////////////////////////////////////
// Context Menu
///////////////////////////////////////////////////////////////////////////////
-const CONTEXTMENU_ID = "output-contextmenu";
-
/*
- * ConsoleContextMenu: This handle to show/hide a context menu item.
+ * ConsoleContextMenu this used to handle the visibility of context menu items.
+ *
+ * @constructor
+ * @param object aOwner
+ * The WebConsoleFrame instance that owns this object.
*/
-let ConsoleContextMenu = {
+function ConsoleContextMenu(aOwner)
+{
+ this.owner = aOwner;
+ this.popup = this.owner.document.getElementById("output-contextmenu");
+ this.build = this.build.bind(this);
+ this.popup.addEventListener("popupshowing", this.build);
+}
+
+ConsoleContextMenu.prototype = {
+ lastClickedMessage: null,
+
/*
* Handle to show/hide context menu item.
- *
- * @param nsIDOMEvent aEvent
*/
build: function CCM_build(aEvent)
{
- let popup = aEvent.target;
- if (popup.id !== CONTEXTMENU_ID) {
- return;
- }
-
- let view = document.querySelector(".hud-output-node");
- let metadata = this.getSelectionMetadata(view);
-
- for (let i = 0, l = popup.childNodes.length; i < l; ++i) {
- let element = popup.childNodes[i];
+ let metadata = this.getSelectionMetadata(aEvent.rangeParent);
+ for (let element of this.popup.children) {
element.hidden = this.shouldHideMenuItem(element, metadata);
}
},
@@ -5042,21 +5367,27 @@ let ConsoleContextMenu = {
/*
* Get selection information from the view.
*
- * @param nsIDOMElement aView
- * This should be <xul:richlistbox>.
- *
+ * @param nsIDOMElement aClickElement
+ * The DOM element the user clicked on.
* @return object
* Selection metadata.
*/
- getSelectionMetadata: function CCM_getSelectionMetadata(aView)
+ getSelectionMetadata: function CCM_getSelectionMetadata(aClickElement)
{
let metadata = {
selectionType: "",
selection: new Set(),
};
- let selectedItems = aView.selectedItems;
+ let selectedItems = this.owner.output.getSelectedMessages();
+ if (!selectedItems.length) {
+ let clickedItem = this.owner.output.getMessageForElement(aClickElement);
+ if (clickedItem) {
+ this.lastClickedMessage = clickedItem;
+ selectedItems = [clickedItem];
+ }
+ }
- metadata.selectionType = (selectedItems > 1) ? "multiple" : "single";
+ metadata.selectionType = selectedItems.length > 1 ? "multiple" : "single";
let selection = metadata.selection;
for (let item of selectedItems) {
@@ -5111,4 +5442,15 @@ let ConsoleContextMenu = {
return shouldHide;
},
+
+ /**
+ * Destroy the ConsoleContextMenu object instance.
+ */
+ destroy: function CCM_destroy()
+ {
+ this.popup.removeEventListener("popupshowing", this.build);
+ this.popup = null;
+ this.owner = null;
+ this.lastClickedMessage = null;
+ },
};
diff --git a/browser/devtools/webconsole/webconsole.xul b/browser/devtools/webconsole/webconsole.xul
index b4e07aa49..2d5d85f6b 100644
--- a/browser/devtools/webconsole/webconsole.xul
+++ b/browser/devtools/webconsole/webconsole.xul
@@ -9,6 +9,8 @@
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/common.css"
type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css"
+ type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/devtools/webconsole.css"
type="text/css"?>
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
@@ -21,14 +23,22 @@
windowtype="devtools:webconsole"
width="900" height="350"
persist="screenX screenY width height sizemode">
+
+ <script type="application/javascript;version=1.8"
+ src="chrome://browser/content/devtools/theme-switching.js"/>
<script type="text/javascript" src="chrome://global/content/globalOverlay.js"/>
- <script type="text/javascript" src="webconsole.js"/>
+ <script type="text/javascript"><![CDATA[
+function goUpdateConsoleCommands() {
+ goUpdateCommand("consoleCmd_openURL");
+ goUpdateCommand("consoleCmd_copyURL");
+}
+ // ]]></script>
<commandset id="editMenuCommands"/>
<commandset id="consoleCommands"
commandupdater="true"
- events="richlistbox-select"
+ events="focus,select"
oncommandupdate="goUpdateConsoleCommands();">
<command id="consoleCmd_openURL"
oncommand="goDoCommand('consoleCmd_openURL');"/>
@@ -37,28 +47,27 @@
<command id="consoleCmd_clearOutput"
oncommand="goDoCommand('consoleCmd_clearOutput');"/>
<command id="cmd_find" oncommand="goDoCommand('cmd_find');"/>
- <command id="cmd_fullZoomEnlarge" oncommand="goDoCommand('cmd_fontSizeEnlarge');"/>
- <command id="cmd_fullZoomReduce" oncommand="goDoCommand('cmd_fontSizeReduce');"/>
- <command id="cmd_fullZoomReset" oncommand="goDoCommand('cmd_fontSizeReset');"/>
+ <command id="cmd_fullZoomEnlarge" oncommand="goDoCommand('cmd_fontSizeEnlarge');" disabled="true"/>
+ <command id="cmd_fullZoomReduce" oncommand="goDoCommand('cmd_fontSizeReduce');" disabled="true"/>
+ <command id="cmd_fullZoomReset" oncommand="goDoCommand('cmd_fontSizeReset');" disabled="true"/>
<command id="cmd_close" oncommand="goDoCommand('cmd_close');" disabled="true"/>
</commandset>
<keyset id="consoleKeys">
- <key id="key_fullZoomReduce" key="&fullZoomReduceCmd.commandkey;" command="cmd_fullZoomReduce" modifiers="accel"/>
- <key key="&fullZoomReduceCmd.commandkey2;" command="cmd_fullZoomReduce" modifiers="accel"/>
+ <key id="key_fullZoomReduce" key="&fullZoomReduceCmd.commandkey;" command="cmd_fullZoomReduce" modifiers="accel"/>
+ <key key="&fullZoomReduceCmd.commandkey2;" command="cmd_fullZoomReduce" modifiers="accel"/>
<key id="key_fullZoomEnlarge" key="&fullZoomEnlargeCmd.commandkey;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
<key key="&fullZoomEnlargeCmd.commandkey2;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
<key key="&fullZoomEnlargeCmd.commandkey3;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
<key id="key_fullZoomReset" key="&fullZoomResetCmd.commandkey;" command="cmd_fullZoomReset" modifiers="accel"/>
<key key="&fullZoomResetCmd.commandkey2;" command="cmd_fullZoomReset" modifiers="accel"/>
<key key="&findCmd.key;" command="cmd_find" modifiers="accel"/>
- <key key="&clearOutputCmd.key;" command="consoleCmd_clearOutput" modifiers="accel"/>
<key key="&closeCmd.key;" command="cmd_close" modifiers="accel"/>
+ <key key="&clearOutputCtrl.key;" command="consoleCmd_clearOutput" modifiers="control"/>
</keyset>
<keyset id="editMenuKeys"/>
<popupset id="mainPopupSet">
- <menupopup id="output-contextmenu"
- onpopupshowing="ConsoleContextMenu.build(event);">
+ <menupopup id="output-contextmenu" onpopupshowing="goUpdateGlobalEditMenuItems()">
<menuitem id="saveBodiesContextMenu" type="checkbox" label="&saveBodies.label;"
accesskey="&saveBodies.accesskey;"/>
<menuitem id="menu_openURL" label="&openURL.label;"
@@ -72,83 +81,89 @@
</menupopup>
</popupset>
- <box class="hud-outer-wrapper devtools-responsive-container" flex="1">
- <vbox class="hud-console-wrapper" flex="1">
- <toolbar class="hud-console-filter-toolbar devtools-toolbar" mode="full">
- <toolbarbutton label="&btnPageNet.label;" type="menu-button"
- category="net" class="devtools-toolbarbutton webconsole-filter-button"
- tooltiptext="&btnPageNet.tooltip;"
-#ifdef XP_MACOSX
- accesskey="&btnPageNet.accesskeyMacOSX;"
-#else
- accesskey="&btnPageNet.accesskey;"
-#endif
- tabindex="3">
- <menupopup>
- <menuitem label="&btnConsoleErrors;" type="checkbox" autocheck="false"
- prefKey="network"/>
- <menuitem label="&btnConsoleLog;" type="checkbox" autocheck="false"
- prefKey="networkinfo"/>
- <menuseparator id="saveBodiesSeparator" />
- <menuitem id="saveBodies" type="checkbox" label="&saveBodies.label;"
- accesskey="&saveBodies.accesskey;"/>
- </menupopup>
- </toolbarbutton>
- <toolbarbutton label="&btnPageCSS.label;" type="menu-button"
- category="css" class="devtools-toolbarbutton webconsole-filter-button"
- tooltiptext="&btnPageCSS.tooltip;"
- accesskey="&btnPageCSS.accesskey;"
- tabindex="4">
- <menupopup>
- <menuitem label="&btnConsoleErrors;" type="checkbox" autocheck="false"
- prefKey="csserror"/>
- <menuitem label="&btnConsoleWarnings;" type="checkbox"
- autocheck="false" prefKey="cssparser"/>
- </menupopup>
- </toolbarbutton>
- <toolbarbutton label="&btnPageJS.label;" type="menu-button"
- category="js" class="devtools-toolbarbutton webconsole-filter-button"
- tooltiptext="&btnPageJS.tooltip;"
- accesskey="&btnPageJS.accesskey;"
- tabindex="5">
- <menupopup>
- <menuitem label="&btnConsoleErrors;" type="checkbox"
- autocheck="false" prefKey="exception"/>
- <menuitem label="&btnConsoleWarnings;" type="checkbox"
- autocheck="false" prefKey="jswarn"/>
- <menuitem label="&btnConsoleLog;" type="checkbox"
- autocheck="false" prefKey="jslog"/>
- </menupopup>
- </toolbarbutton>
- <toolbarbutton label="&btnPageSecurity.label;" type="menu-button"
- category="security" class="devtools-toolbarbutton webconsole-filter-button"
- tooltiptext="&btnPageSecurity.tooltip;"
- accesskey="&btnPageSecurity.accesskey;"
- tabindex="6">
- <menupopup>
- <menuitem label="&btnConsoleErrors;" type="checkbox"
- autocheck="false" prefKey="secerror"/>
- <menuitem label="&btnConsoleWarnings;" type="checkbox"
- autocheck="false" prefKey="secwarn"/>
- </menupopup>
- </toolbarbutton>
- <toolbarbutton label="&btnPageLogging.label;" type="menu-button"
- category="logging" class="devtools-toolbarbutton webconsole-filter-button"
- tooltiptext="&btnPageLogging.tooltip;"
- accesskey="&btnPageLogging.accesskey;"
- tabindex="7">
- <menupopup>
- <menuitem label="&btnConsoleErrors;" type="checkbox"
- autocheck="false" prefKey="error"/>
- <menuitem label="&btnConsoleWarnings;" type="checkbox"
- autocheck="false" prefKey="warn"/>
- <menuitem label="&btnConsoleInfo;" type="checkbox" autocheck="false"
- prefKey="info"/>
- <menuitem label="&btnConsoleLog;" type="checkbox" autocheck="false"
- prefKey="log"/>
- </menupopup>
- </toolbarbutton>
+ <tooltip id="aHTMLTooltip" page="true"/>
+ <box class="hud-outer-wrapper devtools-responsive-container theme-body" flex="1">
+ <vbox class="hud-console-wrapper devtools-main-content" flex="1">
+ <toolbar class="hud-console-filter-toolbar devtools-toolbar" mode="full">
+ <hbox class="devtools-toolbarbutton-group">
+ <toolbarbutton label="&btnPageNet.label;" type="menu-button"
+ category="net" class="devtools-toolbarbutton webconsole-filter-button"
+ tooltiptext="&btnPageNet.tooltip;"
+ accesskeyMacOSX="&btnPageNet.accesskeyMacOSX;"
+ accesskey="&btnPageNet.accesskey;"
+ tabindex="3">
+ <menupopup id="net-contextmenu">
+ <menuitem label="&btnConsoleErrors;" type="checkbox" autocheck="false"
+ prefKey="network"/>
+ <menuitem label="&btnConsoleWarnings;" type="checkbox" autocheck="false"
+ prefKey="netwarn"/>
+ <menuitem label="&btnConsoleXhr;" type="checkbox" autocheck="false"
+ prefKey="netxhr"/>
+ <menuitem label="&btnConsoleLog;" type="checkbox" autocheck="false"
+ prefKey="networkinfo"/>
+ <menuseparator id="saveBodiesSeparator" />
+ <menuitem id="saveBodies" type="checkbox" label="&saveBodies.label;"
+ accesskey="&saveBodies.accesskey;"/>
+ </menupopup>
+ </toolbarbutton>
+ <toolbarbutton label="&btnPageCSS.label;" type="menu-button"
+ category="css" class="devtools-toolbarbutton webconsole-filter-button"
+ tooltiptext="&btnPageCSS.tooltip2;"
+ accesskey="&btnPageCSS.accesskey;"
+ tabindex="4">
+ <menupopup id="css-contextmenu">
+ <menuitem label="&btnConsoleErrors;" type="checkbox" autocheck="false"
+ prefKey="csserror"/>
+ <menuitem label="&btnConsoleWarnings;" type="checkbox"
+ autocheck="false" prefKey="cssparser"/>
+ <menuitem label="&btnConsoleReflows;" type="checkbox"
+ autocheck="false" prefKey="csslog"/>
+ </menupopup>
+ </toolbarbutton>
+ <toolbarbutton label="&btnPageJS.label;" type="menu-button"
+ category="js" class="devtools-toolbarbutton webconsole-filter-button"
+ tooltiptext="&btnPageJS.tooltip;"
+ accesskey="&btnPageJS.accesskey;"
+ tabindex="5">
+ <menupopup id="js-contextmenu">
+ <menuitem label="&btnConsoleErrors;" type="checkbox"
+ autocheck="false" prefKey="exception"/>
+ <menuitem label="&btnConsoleWarnings;" type="checkbox"
+ autocheck="false" prefKey="jswarn"/>
+ <menuitem label="&btnConsoleLog;" type="checkbox"
+ autocheck="false" prefKey="jslog"/>
+ </menupopup>
+ </toolbarbutton>
+ <toolbarbutton label="&btnPageSecurity.label;" type="menu-button"
+ category="security" class="devtools-toolbarbutton webconsole-filter-button"
+ tooltiptext="&btnPageSecurity.tooltip;"
+ accesskey="&btnPageSecurity.accesskey;"
+ tabindex="6">
+ <menupopup id="security-contextmenu">
+ <menuitem label="&btnConsoleErrors;" type="checkbox"
+ autocheck="false" prefKey="secerror"/>
+ <menuitem label="&btnConsoleWarnings;" type="checkbox"
+ autocheck="false" prefKey="secwarn"/>
+ </menupopup>
+ </toolbarbutton>
+ <toolbarbutton label="&btnPageLogging.label;" type="menu-button"
+ category="logging" class="devtools-toolbarbutton webconsole-filter-button"
+ tooltiptext="&btnPageLogging.tooltip;"
+ accesskey="&btnPageLogging.accesskey3;"
+ tabindex="7">
+ <menupopup id="logging-contextmenu">
+ <menuitem label="&btnConsoleErrors;" type="checkbox"
+ autocheck="false" prefKey="error"/>
+ <menuitem label="&btnConsoleWarnings;" type="checkbox"
+ autocheck="false" prefKey="warn"/>
+ <menuitem label="&btnConsoleInfo;" type="checkbox" autocheck="false"
+ prefKey="info"/>
+ <menuitem label="&btnConsoleLog;" type="checkbox" autocheck="false"
+ prefKey="log"/>
+ </menupopup>
+ </toolbarbutton>
+ </hbox>
<toolbarbutton class="webconsole-clear-console-button devtools-toolbarbutton"
label="&btnClear.label;" tooltiptext="&btnClear.tooltip;"
accesskey="&btnClear.accesskey;"
@@ -160,17 +175,21 @@
placeholder="&filterOutput.placeholder;" tabindex="2"/>
</toolbar>
- <richlistbox class="hud-output-node" orient="vertical" flex="1"
- seltype="multiple" context="output-contextmenu"
- style="direction:ltr;" tabindex="1"/>
-
- <hbox class="jsterm-input-container" style="direction:ltr">
- <stack class="jsterm-stack-node" flex="1">
- <textbox class="jsterm-complete-node" multiline="true" rows="1"
- tabindex="-1"/>
- <textbox class="jsterm-input-node" multiline="true" rows="1" tabindex="0"/>
- </stack>
+ <hbox id="output-wrapper" flex="1" context="output-contextmenu" tooltip="aHTMLTooltip">
+ <div xmlns="http://www.w3.org/1999/xhtml" id="output-container"
+ tabindex="0" role="document" aria-live="polite" />
</hbox>
+ <notificationbox id="webconsole-notificationbox">
+ <hbox class="jsterm-input-container" style="direction:ltr">
+ <stack class="jsterm-stack-node" flex="1">
+ <textbox class="jsterm-complete-node devtools-monospace"
+ multiline="true" rows="1" tabindex="-1"/>
+ <textbox class="jsterm-input-node devtools-monospace"
+ multiline="true" rows="1" tabindex="0"
+ aria-autocomplete="list"/>
+ </stack>
+ </hbox>
+ </notificationbox>
</vbox>
<splitter class="devtools-side-splitter"/>